首页 > 其他分享 >bc-dev-hplg-merge-0

bc-dev-hplg-merge-0

时间:2024-05-01 15:45:06浏览次数:12  
标签:Fabric 网络 dev 交易 merge 应用程序 区块 我们 hplg

Hyperkedge 区块链开发教程(全)

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

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

这个学习路径是你探索和构建使用以太坊、Hyperledger Fabric 和 Hyperledger Composer 的区块链网络的简易参考。它从区块链的概述开始,向您展示如何为开发、打包、构建和测试去中心化应用程序设置以太坊开发环境。您将学习 Solidity - 在以太坊中开发去中心化应用程序的事实上语言。您将配置 Hyperledger Fabric,并使用这些组件构建私有区块链网络和连接到它们的应用程序。从原则出发,您将学习设计和启动网络、在链代码中实现智能合约,以及更多。

在完成这个学习路径时,您将能够通过处理区块链生命周期中遇到的关键痛点来构建和部署自己的去中心化应用程序。

本书适合谁

这个学习路径是为了想要从零开始使用 Hyperledger 构建去中心化应用和智能合约的区块链开发者而设计的。对于任何编程语言的基本了解或接触都将有助于开始本课程。

本书涵盖内容

第一章区块链 - 企业和行业视角,你已经听说过区块链,想知道为什么会有这么多吵闹?在本章中,我们探讨了为什么区块链是一个颠覆性创新,它带来了什么创新,以及技术领域是什么。

第二章探索 Hyperledger Fabric,从理解区块链格局开始,然后我们将注意力转向 Hyperledger Fabric。本章的目标是在揭示/构建架构的同时,带领您逐步部署 Hyperledger Fabric 的每个组件。

第三章用业务场景做铺垫,描述了一个业务用例,然后专注于理解从需求到设计使用区块链创建良好业务网络的过程。

第四章使用 Golang 设计数据和交易模型,旨在定义 Hyperledger Fabric 中智能合约的组成部分。它还将向您介绍有关智能合约的一些术语,并让您体验使用 Go 语言开发链代码的过程。

第五章暴露网络资产和交易,利用上一章中编写的智能合约,本章关注应用程序与网络的必要集成。它通过配置频道、安装和调用链代码来带领读者从客户端应用程序考虑可能使用的各种集成模式。

第六章商业网络,旨在介绍和揭示建模商业网络所需的技能和工具。在更高层次的抽象水平上工作,基础、工具和框架将为读者提供一种快速建模、设计和部署完整端到端的商业网络的方法。

第七章一个商业网络示例,将前一章节的概念付诸实践,本章将步骤性地介绍如何部署一个完整的商业网络,从终端用户应用到智能合约。

第八章区块链网络中的灵活性,重点介绍了在区块链网络中保持灵活性所需的方面。应用 DevOps 概念,向读者呈现了一个持续集成 / 持续交付管道。

第九章区块链网络中的生活,旨在提高读者对采用分布式分类帐解决方案时组织和联盟可能面临的关键活动和挑战的认识,从应用程序变更的管理到维护足够的性能水平。一个成功的网络部署将有望看到许多组织加入其中,以及交易数量的增加。

第十章治理 - 受监管行业的必要之恶,治理对于受监管行业来说是必要之恶,但治理不仅适用于处理受监管行业用例的商业网络。这也是一种确保商业网络长期性和可扩展性的良好实践。本章探讨了任何创始人领导的区块链网络生产准备的重要考虑因素。

第十一章超级账本 Fabric 安全,为区块链网络的安全设计打下基础。讨论了各种安全构造,并详细解释了超级账本 Fabric 的安全性。这是理解安全性设计考虑的重要章节。

第十二章区块链技术简介,概述了关键概念,如密码学和哈希算法、分布式分类帐、交易、区块、工作证明、挖矿和共识。我们详细介绍了比特币,区块链技术的鼻祖。我们通过指出比特币的一些局限性以及以太坊是如何解决这些问题的来简要介绍以太坊。虽然比特币和以太坊是公共区块链的例子,但 IBM 的超级账本用作企业区块链的示例。在本章末尾,我们将审视区块链的演进,从 1.0、2.0、3.0 等等,以及它们的用例。

第十三章以太坊基础知识,涵盖了以太坊的基本概念,如智能合约、以太币、共识算法、以太虚拟机(EVM)、Gas 和账户等。我们将讨论以太坊的性能,并探讨如何通过工作证明、卡斯珀、Plasma 和分片等方式提高整体性能。

第十四章Solidity 编程概览,讨论了什么是 solidity,以及 solidity 开发环境的工具。然后我们讨论智能合约及其常见模式。我们涵盖了智能合约安全的重要主题。最后,我们展示了如何编写众筹用例的智能合约。

第十五章构建以太坊区块链应用程序,探讨了什么是 DApp。我们快速概述了 web3.js。我们解释了如何设置以太坊开发环境,以及如何开发和测试 DApp。

第十六章使用 Hyperledger Fabric 探索企业区块链应用程序,深入介绍了 Hyperledger Fabric 的关键概念,以及核心组件。我们解释了如何创建一个 Hyperledger Fabric 环境,如何编写链码,以及如何设置 Hyperledger Fabric 配置。

第十七章使用 Hyperledger Composer 实现业务网络,提供了对 Hyperledger Composer 的概述,讨论了如何设置 Hyperledger Composer 环境。我们讨论了业务场景、业务网络归档以及如何实现业务交易功能。

第十八章区块链使用案例,首先讨论了跨行业流行的区块链使用案例,包括金融领域、公共服务、供应链、物联网IoT)和医疗保健等。然后我们将继续讨论 DApps 的适当使用案例,然后开发一个成功的 DApp。最后,我们以健康数据共享的使用案例为例,就如何建立适合的 DApp 进行高层次评论。

充分利用本书

我们专注于组织和流程。内容确保不仅易于跟随和自然流动,而且具有主题模块化性。每一章探索了区块链的一个方面。虽然专门讨论了 Hyperledger 项目,但核心关注领域是适用于区块链技术学科的。

本学习路径旨在成为进入区块链技术世界的发展路径。这些章节的安排旨在确保可以轻松跟随并自然流畅地展开。

商业用户可以跳过详细描述如何开发区块链应用程序的章节,而是专注于对技术和使用案例的概述的章节。

建议 IT 用户下载代码并进行修改,以适应他们自己的用例或练习。

下载示例代码文件

你可以在 www.packt.com 的账户中下载本书的示例代码文件。如果你在其他地方购买了这本书,你可以访问 www.packt.com/support 并注册,文件将直接通过电子邮件发送给你。

您可以按以下步骤下载代码文件:

  1. 登录或注册 www.packt.com

  2. 选择“SUPPORT”选项卡。

  3. 点击“Code Downloads & Errata”。

  4. 在搜索框中输入书名,然后按照屏幕上的指示操作。

下载文件后,请确保使用最新版本的解压缩软件解压文件夹:

  • Windows 上使用 WinRAR/7-Zip

  • Mac 上使用 Zipeg/iZip/UnRarX

  • Linux 上使用 7-Zip/PeaZip

本书的代码捆绑包还托管在 GitHub 上,网址为 github.com/PacktPublishing/Blockchain-Development-with-Hyperledger。如果代码有更新,将在已存在的 GitHub 存储库中进行更新。

我们还有其他代码捆绑包来自我们丰富的图书和视频目录,可在 github.com/PacktPublishing/ 上查看!

使用规范

书中的代码词汇,数据库表名,文件夹名称,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 账号显示如下:"发单方属于自己的组织,被称之为 TradeOrdererOrg。"

一段代码块设置如下:

- &ExporterOrg
  Name: ExporterOrgMSP
  ID: ExporterOrgMSP
  MSPDir: crypto-config/peerOrganizations/exporterorg.trade.com/msp
  AnchorPeers:
    - Host: peer0.exporterorg.trade.com
    Port: 7051

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:

pragma solidity ⁰.4.15;
import 'zeppelin/contracts/math/SafeMath.sol';
….
contract ExampleCoin is ERC20 {
  //SafeMath symbol is from imported file SafeMath.sol'
  using SafeMath for uint256;
   …
}

任何命令行输入或输出,请按如下方式写成:

mkdir ~/insurance-claim && cd ~/insurance-claim

粗体:表示一个新术语,重要词汇,或在屏幕上看到的词汇。例如,菜单或对话框中的词汇会显示在文本中像这样。例如: "当前请求经过矿工节点验证后,将调用 HelloWorld 智能合约。"

警告或重要注释看起来像这样。

小贴士和技巧看起来像这样。

联系我们

我们非常欢迎读者的反馈意见。

常规反馈:如果你对本书的任何方面有疑问,请在邮件主题中提及书名,并发送邮件至 [email protected]

勘误:尽管我们已尽一切努力确保内容的准确性,但错误确实会发生。如果您在本书中发现错误,我们将不胜感激。请访问 www.packt.com/submit-errata,选择您的书籍,点击“Errata Submission Form”链接,然后输入详细信息。

盗版:如果你在互联网上发现我们的作品的任何形式的非法拷贝,我们将不胜感激,如果你能提供给我们位置地址或网站名称。请通过[email protected]与我们联系,并附上材料的链接。

如果你有兴趣成为作者:如果你在某个专业领域有专长,并且对写作或为书籍做出贡献感兴趣,请访问authors.packtpub.com

评论

请留下评论。在阅读和使用本书后,为什么不在你购买的网站上留下评论呢?潜在的读者可以看到并使用你的客观意见来做出购买决定,我们在 Packt 可以了解你对我们产品的看法,我们的作者也可以看到你对他们书籍的反馈。谢谢!

欲了解更多有关 Packt 的信息,请访问 packt.com

第一章:区块链 - 企业和行业视角

区块链承诺从根本上解决时间和信任问题,以解决金融服务、供应链、物流和医疗保健等行业的效率和成本问题。区块链的关键特征包括不可变性和共享账本,在这个账本上,交易更新由共识驱动的信任系统执行,这可以促进多方之间真正的数字交互。

这种数字交互不仅受制于系统信任,而且确保交易记录的来源保持不可变的互动记录。这个特点本身就促使了可追责性和不可否认性,并激励公平竞争。通过区块链系统设计,我们试图建立一个隐含信任的系统。这种信任系统导致了风险的降低,以及各种应用技术构建,如密码学、加密、智能合约和共识,本质上创造了门槛,不仅降低了风险,而且还为交易系统注入了额外的安全性。

在本章讨论中,我们将涵盖区块链的以下几个方面:

  • 定义区块链

  • 区块链解决方案的构建模块

  • 安全交易处理协议的基础知识

  • 区块链的应用

  • 企业中的区块链

  • 企业设计原则

  • 选择区块链框架的业务考虑

  • 选择区块链框架的考虑因素

定义术语 - 什么是区块链?

在技术层面上,区块链可以被定义为一个不可变的账本,用于记录交易,由一个互不信任的对等分布式网络维护。每个对等方都保留着账本的副本。对等方执行共识协议来验证交易,将其分组成块,并在块之上构建哈希链。这个过程通过将交易排序为保持一致性所必需的方式来形成账本。区块链随着比特币( bitcoin.org/)的出现而崭露头角,并被广泛认为是在数字世界中运行受信任交易的一项有前途的技术。

支持加密货币的区块链在公开或无许可方面,即任何人都可以参与而无需特定身份。这类区块链通常使用基于工作证明PoW)和经济激励的共识协议。相比之下,许可区块链作为在一群已知、可识别参与者之间运行区块链的替代方式而发展。许可区块链提供了一种确保共享相同目标但不完全信任对方的实体之间相互作用的方式,例如互相交换资金、商品或信息的企业。许可区块链依赖于其对等方的身份,并通过传统的拜占庭容错BFT)共识来实现。BFT 是一种协议,已广泛用于 IT 解决方案中,用于就网络失效节点状态达成一致。该协议基于拜占庭将军问题,即一群将军需要就他们的战略达成一致,但其中一人可能是叛徒。

区块链可以执行智能合约形式的任意可编程交易逻辑,正如以太坊(ethereum.org/)所示。比特币中的脚本是这一概念的前身。智能合约充当可信的分布式应用程序,在其中安全性来自区块链和同行之间的共识。

对于企业希望利用区块链平台的企图,区分许可和无许可区块链至关重要。用例决定了技术的选择,技术取决于共识系统、治理模型、数据结构等。通过使用许可区块链,我们可以以更好的方式做我们已经在做的一些事情,这可能具有重要意义。在接下来的图表中,您可以看到一个银行联盟如何使用超级账本(Hyperledger),这是一种许可区块链类型,用于清算和结算而无需依赖中央清算机构:

清算机构的创建是因为银行彼此之间并不完全信任,在交易之间充当中介降低了一方不履行其义务的风险,这导致了一场关于许可与无许可区块链的漫无止境的讨论,而本章不会涉及这场辩论,区块链可以提供一种改变或颠覆当前业务和商业模式的方式。在受监管行业中,大多数用例采用许可区块链模型。

这是由于监管要求和交易处理的经济可行性,而无论选择的区块链模型如何,区块链都提供了许多转型和颠覆的可能性。

区块链作为一种技术平台具有非凡的潜力。在企业中,区块链可以提供:

  • 一种设计方法,将交易数据、价值和状态固有地靠近业务逻辑

  • 通过社区验证的安全业务交易执行,在安全流程中促进信任和坚固的交易处理,这是区块链基础的。

  • 符合现有法规的替代、权限化技术

区块链承诺解决长期存在的行业问题——这正是它的潜力所在,解决类似于现代化金融和贸易系统的问题,加速证券和贸易结算。

区块链框架的四个核心构建模块

区块链框架通常包括以下四个构建模块:

  • 共享账本:共享账本仅附加分布式交易记录。比特币区块链的设计初衷是民主化可见性;然而,使用区块链时,也需要考虑消费者数据法规。使用配置正确的 SQL 或 noSQL 分布式数据库可以实现不可变性或仅附加的语义。

  • 密码学:区块链中的密码学确保身份验证和可验证的交易。区块链设计包括这一重要因素,因为关注于假定计算难度和使加密对对手更难破解。这是比特币区块链的一个有趣挑战,因为涉及经济激励和其系统设计。在一个不那么民主或权限的商业账本网络中工作时,对密码学的考虑会改变。

  • 信任系统或共识系统:信任系统是指利用网络的力量来验证交易。

    在我看来,信任系统是区块链系统的核心;它们是区块链应用的核心,并且我们相信“信任系统”是首选术语,而不是共识系统,因为不是所有的验证都是通过共识完成的。这种信任系统的基础元素决定了对区块链基础设施的整体设计和投资。随着区块链领域的每一个新参与者,信任系统都在修改,形成了为特定的区块链用例专门化的变体。信任、交易和所有权是区块链技术的基本要素。对于公司间交易,信任系统管理着参与公司之间的交易。

    仍然需要做很多工作来定义特定用例的最佳信任系统,比如 P2P 和共享经济模式与 B2B 模式。

  • 业务规则或智能合约: 智能合约是内嵌在区块链交易数据库中并通过交易执行的业务条款。这也是区块链解决方案的规则组成部分。需要定义每个交易的价值流和状态。

以下的使用图表很好地解释了这些概念:

这四个基本模块是被广泛接受和理解的。它们在区块链出现数十年前就存在了。共享账本是一种进化性的变化,类似于从纸张表格过渡到基于计算机的电子表格,但根本的业务规则保持不变。

其他需要考虑的能力

企业区块链提案中还应包括什么?以下是需要考虑的其他能力的非穷尽列表:

  • 审计和日志记录: 在区块链解决方案中包含审计和日志记录可以帮助解决法规,用于不可否认、技术根本原因分析、欺诈分析和其他企业需求。

  • 企业集成: 还值得考虑解决方案将如何集成到企业中:

    • 与现有记录系统的集成(*SoR)的目标是确保区块链解决方案支持您现有的系统,如 CRM、商业智能、报告和分析等等

    • 作为交易处理系统的集成: 如果您想保留记录系统作为采纳区块链的临时方法,将其集成为交易处理系统是有意义的

    • 具有包含区块链的意图的设计: 对现有系统产生最小影响的路径将加速企业对区块链的采用。

  • 监控: 监控对于解决法规问题和确保高可用性、容量规划、模式识别和故障识别非常重要。

  • 报告和监管要求: 准备好解决监管问题对于区块链作为交易处理系统的临时采用也非常重要。建议您与现有的 SoR 对接,以卸载报告和监管要求,直到区块链变得企业感知,或者企业软件对区块链感知。

  • **企业身份验证、授权和会计 **要求: 在受许可的企业世界中(不同于无需许可的比特币区块链),所有区块链网络参与者应被识别和跟踪。如果他们要在生态系统中扮演角色,他们的角色需要被定义。

安全事务处理协议的基本原理

我们之前提到,密码学是区块链解决方案的核心构建模块之一。比特币区块链的基本安全性在于账本的所有主要组件之间的优雅的密码连接。具体来说,交易之间通过默克尔树主要相互连接。默克尔树基于树数据结构的概念,其中每个叶节点都有其数据的哈希计算,而非叶节点具有其所有子节点的哈希。这种方法不仅提供了确保数据完整性的方式,而且通过允许删除被视为私有的叶子但保留哈希来提供隐私特性,从而保持了树的完整性。默克尔树的根已经合并到块头中。块头包括对其之前的块头的引用。

密码学强制的相互连接促进了分布式分类账的稳定性和安全性。在任何时候,如果任何组件之间的连接断开,都会使它们容易受到恶意攻击:

交易也通过默克尔树与区块链结构的其余部分进行了密码学连接。一旦在一个块中修改了交易,并且其他所有部分保持稳定,那么该块的所有交易与其头之间的链接就会断开:

新生成的默克尔树根与已经在块头中的根不匹配,因此不能连接到区块链的其余部分。如果我们继续更改块头中的默克尔树根,那么我们将破坏块头链,从而破坏区块链本身的安全模型。因此,如果我们只更改一个块的内容,则区块链的其余部分保持稳定和安全,特别是由于块头通过在下一个块的头部包含上一个块头的哈希来提供连接的链接。

区块链技术的发展历程及未来走向

区块链已经是商业颠覆者,我预计它将在不久的将来显着改变行业、政府和我们的生活。

伟大的分水岭

加密货币和首次代币发行ICO)世界之间存在显着的分歧,以及受监管业务的世界。后者包括银行和金融机构共同努力评估市场潜力和运营效率。

这一分歧的双方都利用了区块链周围的动能来推进各自的利益。区块链生态系统挑战了现状,并且不顾一切地表明了一个观点——通常行为像一个青少年。它受到新商业模式、去中心化的承诺和有趣的技术创新的推动。随着区块链势头的增长,比特币和其他加密资产的价值正在飙升,现在 ICO 已经出现,它打破了传统的筹款监管框架。

在企业方面,有越来越多的行业倡议围绕清算和结算,以实现更快的结算和银行间转账、透明度通过数字化、供应链中信息的对称传播以及在物联网IoT)设备之间创建临时信任。

这里有一个共同的主题——区块链将会留下来。随着它不断发展,并为行业使用案例生成创新解决方案,它将不断迈向成熟,并在信任基础上兑现其效率和显著成本节约的承诺。

区块链交付的经济模型

由区块链技术支持的商业网络可能会给行业带来转型或颠覆,但无论如何,为了蓬勃发展,区块链都需要一个经济模型。如果颠覆是目标,那么技术、人才和市场协同的投资可以与经济激励的诱惑相结合。例如,ICO 通常依赖于 Tokenomics,这是描述这些网络中价值生成经济系统的术语。代币是系统或网络通过为提供者或消费者创造平台,或通过共同创建一个各方都可以利用的自我管理的价值网络来生成的价值单位,以便创建、分发和共享符合所有利益相关者利益的奖励。

大部分由加密货币资助的 ICO 前沿打破了风险资本(由众筹项目领导)当前的筹款机制,而且,区分证券和实用币的区别是原则上具有颠覆性的。

ICO 正在寻求创建一个建立在去中心化开放治理(或自我治理)和透明度原则上的经济体系,一个奖励创新并消除去中心化的系统。ICO 看到了一些初期的失败和一些成功,但它们仍然提供了未来的预览,在那里加密资产将成为一个基本的价值单位——其估值和可替代性由它们来源的网络定义——推动着一个建立在和围绕创新的经济体。

在企业方面,更加关注理解技术并重新构想生态系统、商业网络、法规、保密性和隐私以及影响各行业区块链网络的商业模式。希望探索区块链的企业想要看到快速的验证点,能够迅速展示结果并帮助他们与区块链进行创新的用例。

区块链通过提供内置的交易数据控制、出处和历史背景,帮助各行业实现信息更对称地传播。这可以带来更高效的工作流程和转变的业务流程。然而,许多早期项目并没有关注区块链的核心原则,导致了中介去除、去中心化和强大的自我治理模式。不过,这背后也有一个很好的理由:行业和传统企业往往专注于当前的商业议程、模式、增长以及最重要的,法规合规和遵从。对当前业务运营的强调意味着它们并不自然地倾向于颠覆性模式。

边学边做

对于任何新技术,都存在一个学习曲线。随着区块链的发展,我们开始与受监管的行业合作,我们很快意识到在这些行业中,有一些重要的设计考虑需要解决,比如保密性、隐私、可扩展性和性能等。当涉及到设计区块链网络以及管理这些网络的商业模式时,这些元素可能会产生重大的成本影响。这些挑战不仅很有趣,而且对传统的受监管行业和企业产生了积极的影响,通过重新激发这些组织中的创新,并邀请最优秀的人才加入解决这些挑战。企业正在看到,由区块链技术驱动的生态系统和网络将有助于进步和成功。

需要开始揭示一种激励模式,以激励组织加入一个促进奖励的平台的想法,从而使所有利益相关者受益。 tokenomics 背后的经济激励不能被很多传统企业和行业盲目采纳,但这并不意味着这些行业不应该开始探索可能的商业模式,以实现价值创造,并推动一些急需的现代化努力。

信任和问责制的承诺

区块链技术承诺成为一个安全交易网络的基础,可以在许多饱受信任和问责制系统性问题困扰的行业中诱发信任和安全。从技术角度来看,区块链促进了一个安全、透明、可审计、高效和不可变的交易处理和记录系统。这些技术特征适用于解决当前分布式交易系统所困扰的时间和信任问题。

区块链从根本上改变了多层模型,转向了扁平层的交易处理模型。这有望通过去中介化,通过在新的系统设计中引入高效性,或简单地创造新的商业模式来从根本上削弱行业。

去中介化表示减少生产者和消费者之间的中介使用,比如直接在证券市场投资而不是通过银行进行交易。在金融行业,每笔交易历来都需要有一方来处理交易。去中介化包括移除中间商,从定义上来说这会破坏基于中介的商业模式和激励经济体。近年来,由数字技术带来的波澜涌起,这正是由市场洞察和组织提供更丰富的用户体验的欲望所推动。

区块链是一项旨在通过引入交易、信任和所有权的技术来推动这一变革。区块链数据库和记录所代表的技术模式具有潜力从根本上改善银行业、供应链和其他交易网络,为创新和增长提供新机会,同时降低成本和风险。

把区块链技术应用到工作中的行业

让我们简单看一下区块链的应用场景:

企业中的区块链

现在我们已经看到区块链在各行业中的发展,让我们谈谈企业应该如何使用区块链的原则。企业为什么要将区块链技术应用于其系统或应用程序之一?

哪些应用适合?

组织需要在应用设计过程中建立使用标准以帮助他们评估应该在哪些方面最好地应用区块链技术。以下是一些标准的例子,可以帮助企业确定哪些应用或系统将受益于此:

  • 遵循交易、信任和所有权原则的应用:如先前所述,这三个原则——交易、信任和所有权对于任何区块链系统都是至关重要的。交易和所有权意味着分类账条目的变更和转移,而信任指向交易系统的无需信任的性质。

  • 基本上是交易性质的应用:关于为什么我们不能从分布式数据库,即非 SQL 或关系型数据库中获得区块链的好处,经常会有争论。但是多方交易是使应用适合区块链的关键。这需要有长时间运行的进程,有大量微型交易将由区块链支持的交易系统验证和验证。然而,数据库仍然可以用于持久性或复制以适应企业系统。其他考虑因素包括可能会随时间增加的小数据集大小、日志开销等。

  • 由非垄断参与者组成的商业网络:这第三个标准涉及分布式与去中心化计算模型。区块链信任系统可以在任何模型中工作;然而,区块链商业网络的信任方面来自具有非垄断参与(联合许可网络模型)的多方参与者。垄断参与可能是可以接受的(私有许可网络模型),但是必须制定一种信任模型,确保即使参与者具有理性行为,也能防止中心化控制。许多内部用例不遵循这一原则,更多地用于分布式应用程序模型。

对于试图理解或确定在哪里有意义地应用区块链的企业,有一种简单的方法可以思考用例选择。一个适当的用例对于可持续的区块链解决方案将实现长期业务目标,并提供强大的技术投资回报。

这始于一个企业问题——一个足够大,以至于企业要花费资源/时间的问题——以及认识到具有相同问题的同伴。当公司意识到企业问题也是行业问题(如安全借贷、抵押借贷等)时,他们找到了区块链潜力最大的用例。

当组织正在确定区块链的各个方面对其企业应用的益处时,他们还需要认识到整个区块链领域的碎片化。有许多创新方法可用于解决特定挑战与区块链。许多供应商提供专门用于解决特定用例的信任系统的变体,并且他们已经定义了在给定行业中区块链将最受益的用例,例如。这些专业供应商通常承诺提供快速解决方案,以满足消费者对快速数字交互的需求。

区块链的原则在传递快速的消费者驱动的结果方面非常有帮助,比如分散的、分布式的、全球的、永久的、基于代码的可编程资产,以及交易记录。我们在考虑将区块链视为解决每个企业应用的问题的工具时要小心,但它可以在许多交易应用中起到作用。

现在,让我们讨论企业对区块链的看法,以及企业采用这项技术所面临的一些挑战。在接下来的部分中,我将重点关注三个领域,这些领域有助于在企业背景下确定区块链的基调。

企业如何看待区块链?

激进的开放性是区块链作为数字信任网络的一个方面,但在企业中,考虑激进的开放性的影响和意义是至关重要的。

公共区块链可以以极其简单的方式运作,支持所有交易的高度分布式的主列表,通过匿名共识支持的信任系统进行验证。但企业能直接应用无信任系统的模式而不修改区块链的基本原则吗?

组织将这种颠覆性技术视为他们变革的一条道路,还是仅仅是帮助他们改进现有流程以利用信任系统所承诺的效率的工具?无论怎样,企业都希望对区块链的采用对现有系统的干扰尽可能小,并且这并不容易实现!毕竟,现有系统的设计缺陷正是促使企业考虑这种范式转变的原因。很多关于区块链的概念和用例距离企业的实际应用还有很远的路要走。

第一个尝试和采用区块链的行业是金融服务领域,因为他们一直面临着被另一波创业公司颠覆的恐惧。像许多行业一样,它也受到了消费者对更快速、低成本交易的需求的驱动。金融服务有一系列明确定义的用例,包括贸易融资、贸易平台、支付和汇款、智能合同、众筹、数据管理和分析、市场借贷以及区块链技术基础设施。我们在这个行业看到的对区块链的应用可能会渗透到未来的其他行业,比如医疗保健、零售和政府。

区块链是一种新兴技术,汇集了许多好的想法,但对于企业的使用仍需进一步发展。在多领域链之间促进互操作性的缺乏明确定义的标准可能是一个挑战。因此采用它的企业将需要建立能力,以便他们可以为进一步的创新作出贡献,并帮助必要的区块链标准开发。这反过来可能有助于为改进现有业务实践和在基于区块链的信任网络中开发新业务模式提供独特的机会:

用于证明区块链技术应用的滤纸试验

从根本上讲,区块链解决了交易经济的三个方面:

  • 交易

  • 拥有权

  • 信任

区块链的显著技术元素包括:

  • 信任系统背后的技术:共识、挖掘和公开账簿

  • 开放网络上的隐秘通信:密码学和加密

  • 不可否认的系统:可见性到一堆过程

虽然区块链技术的影响可能很深远,但组织应制定一套特定于企业的标准,可应用于可能偏向企业区块链的现有或新项目。

鉴于区块链技术的多功能性和当前的炒作曲线,企业应该使用链决策矩阵作为工具,以确保他们对待业务领域应用基础技术的方法是有结构的。这种方法也将有助于一个一致的区块链基础设施和信任系统管理,这在许多应用驱动链发展并且对企业的可见性、管理和控制需求增长的情况下将非常重要。

为整个企业集成区块链基础设施

任何企业采用区块链技术都应该以颠覆现有系统为目标。考虑与企业的记录系统集成是朝这个方向努力的一种方式。通过这种方式,企业可以实现基于区块链的交易处理,并将其现有的记录系统用作其其他应用程序(如业务智能、数据分析、监管互动和报告)的接口。

将企业区块链技术的基础设施与利用链技术获得竞争优势的业务领域分开是至关重要的。区块链可以被视为一种对企业不可见的企业链基础设施,它在幕后运作,同时促进各种业务驱动链之间的企业协同。这个想法是将业务领域与支持它的技术分开。链应用应由具有合适信任系统的业务领域配置。正如我一再强调的那样,信任系统对任何区块链努力都至关重要,因此它应该符合特定业务应用的需求。基础设施和计算需求的成本将由企业可用的信任系统的选择决定。

通过将区块链技术基础设施分离出来,围绕可插拔的信任系统设计架构,利用信任中介和促进灵活性的设计以及模块化信任系统,企业可以专注于业务和监管要求,如 AML、KYC、不可否认等。区块链应用的技术基础设施应该是开放的、模块化的,并且适用于任何区块链变种,从而使区块链努力易于管理。

企业协同表明要推动多个企业区块链之间的协同,以实现企业内部和企业间链(跨链)连接。在这种模式下,交易将穿过各种信任系统,使企业治理和控制系统能够看到交互行为。在审视业务部门和外部企业之间的这些交互作用时,应考虑分形可见性及其关联的企业数据保护。一个无形的企业链基础设施可以为发展企业连接器和公开 API 提供坚实的基础,使现有系统更具链感知能力。

由于业务链之间的有条件可编程合约(智能合约),企业协同将得到促进:

企业如何知道自己是否已准备好采用区块链?更重要的是,在考虑区块链消费时,它应该将重点放在与现有交易系统的整合上,还是应该考虑企业感知的区块链基础设施?

要充分利用企业区块链的承诺,一个整合型企业将需要不止一个用例,并且需要推动企业协同。最成功的区块链消费策略应该首先关注技术,然后考虑与现有企业业务系统的整合。这将促进集体理解,并加速企业采用区块链的过程,希望能够选择最少干扰的路径。

企业设计原则

正如先前所述,区块链技术承诺成为一个安全交易网络的基础,在受到围绕信任和问责制的系统性问题困扰的行业中引发信任和安全感。它旨在产生市场和成本效益。

在过去的几年里,随着区块链技术的成熟,我们关注的重点是企业和商业如何利用这项技术来解决痛点并开启新的商业模式。已经开始看到区块链潜力的组织现在开始重塑受陈旧流程、文书工作和技术成本困扰的业务网络。

业务驱动和演进

在最近的过去,组织会将内部业务系统和 IT 基础设施延伸到互联网上,以利用互联和可访问系统的协作潜力。区块链技术正在将这一趋势推向新的高度,提供由可信赖的业务网络促成的真正数字化互动。在互联网时代,成功的企业采用并适应了技术挑战,而在区块链时代,业务而不是技术成为了推动力。

虽然区块链技术本身很有趣,但业务网络还涉及许多其他机制,这些机制也应该进行评估,包括:

  • 共识模型:哪种信任体系最适合您的业务网络?

  • 控制和治理:允许哪些实体做什么?如果系统出现异常,调查过程将由谁来负责?

  • 数字资产生成:谁在系统中创建资产?谁来管理它?

  • 发行权力:在一个真正分散的系统中,权威的概念不再具有一致性。那么在区块链网络中,谁将负责治理、追究责任,以及最终的监管呢?

  • 安全考虑:网络将如何解决企业安全问题,包括共享业务网络带来的新安全挑战?

我们设想一个专为多个业务领域而设的区块链网络,例如,抵押贷款、支付、交易、特定资产类型的清算和结算等。在企业环境中,我们设想一个中心化的网络,其中志同道合的业务实体共享一个共识联盟。支持这一中心化网络概念的几个实际理由包括以下几点:

  • 使用特定领域的业务语言,导致智能合约的构建、管理和治理,作为代理业务表示的一部分

  • 定义了一种资产类型,导致数字资产的治理、管理和估值(用于交易、可替代性等)

  • 适当的监管,考虑到每个行业和业务网络都有单独的监管,因此遵守监管和其他相关成本的负担可以在业务网络中共享。

  • 其他相关业务功能,如分析、分析、市场数据等

我们已经介绍了企业区块链的商业动力,接下来让我们考虑如何确保区块链网络的可持续性和长期性。

确保可持续性

基于区块链的业务网络正在不断发展壮大,随着它们的发展,核心问题如信任模型、数据可见性和利用网络获取竞争优势等问题将不可逆转。

关注可持续性似乎是矛盾的,因为它促进了开放的协作创新,同时又锁定了诸如共识或信任系统以及用于管理资产、智能合约和多方交易网络中的整体交互的治理系统等结构。区块链系统设计需要考虑所有这些因素。

成功的系统设计需要与多方场景中的区块链原则(包括贸易、信任、所有权和交易性)相匹配。如果不基于这些核心原则构建业务网络,可能无法以可持续的方式实现区块链技术的承诺。

以下是支持和维持区块链业务网络增长的七项设计原则:

  • 网络参与者需要控制自己的业务

  • 网络必须是可扩展的,以便参与者可以灵活地加入或离开网络

  • 网络必须是经过许可的,但也必须受保护,以保护竞争数据,同时促进点对点交易

  • 网络应允许开放访问和全球协作进行共享创新

  • 网络必须可扩展,既可用于事务处理又可用于加密数据处理

  • 网络必须能够容纳企业安全,并应对新的安全挑战

  • 网络需要与企业中已建立的记录系统和交易系统共存

我们将以图形方式列出设计原则,如下所示:

推动区块链采用的原则

在任何企业中,区块链采用都是由三个原则驱动的:业务蓝图、技术蓝图和企业集成。

选择区块链框架时,根据以下三个原则考虑一些不可或缺的事项:

  • 业务蓝图:区块链承诺创建一个基于信任的价值业务网络。为了做到这一点,理解各种区块链框架如何处理网络交互模式、低效和漏洞至关重要。

  • 技术蓝图: 如果技术要与业务目标保持一致,组织就需要为其需求做出适当的技术和架构选择。在这里可能会考虑每秒交易数(TPS)、企业集成、外部系统集成以及监管和合规性要求。这些决策都是适当预算区块链采用所必需的技术尽职调查的一部分。

  • 企业集成: 将区块链集成到企业系统中,特别是邻接系统,是一个重要的商业和技术考虑因素(因为下游交易系统影响关键业务系统),以及一个成本因素。根据我的经验,如果组织在规划的早期不专注于邻接系统的集成,可能会阻碍采用,因为它对区块链项目的成本影响显著。

在接下来的章节中,我会稍微详细地讨论每个设计考虑因素。

选择区块链框架的业务考虑因素

当组织在评估是否采用区块链来解决他们的痛点时,会涉及到许多标准。以下是一些从业务角度考虑的因素:

  • 开放平台和开放治理: 企业选择的技术标准将为企业区块链采用、合规性、治理以及解决方案的总体成本奠定基础。

  • 解决方案的经济可行性: 无论组织选择哪种区块链框架,都应该提供与其现有业务模型、退款、计算权益和帐户管理的成本对齐。这与投资回报率息息相关。

  • 解决方案的长期性: 当组织努力构建一个可信的网络时,他们希望确保他们能够承担网络的成本和运营,以便它能够增长和扩展以容纳更多的参与者和交易。

  • 监管合规性: 合规性问题与交易处理密切相关,可能包括行业特定的报告和分析事件,用于业务工作流和任务,无论是自动化的还是以人为中心的。

  • 与邻接系统的共存: 区块链网络需要能够与企业的其余部分、网络参与者和邻接系统共存,这些系统可能具有重叠和互补功能。

  • 业务增长的可预测成本: 业务增长依赖于可预测的指标。历史上,许多行业都关注每秒交易数,但是这种测量因系统设计、计算成本和业务流程而异。

  • 技能和人才的获取: 人才的可获得性影响着成本以及随着行业和技术的不断创新,维护和区块链解决方案的长期性。

  • 技术供应商的财务可行性:在选择供应商时,重要的是考虑他们在长期支持和您的区块链解决方案的长期使用方面的可行性。您应该审查供应商或业务伙伴的长期愿景和可持续性的商业模式。

  • 全球范围的支持和支持:区块链解决方案往往涉及具有全球影响力的商业网络,以及支持网络扩张而又最小化中断的相关技能。

  • 依赖技术和行业特定标准:标准至关重要,不仅有助于标准化共享技术堆栈和部署,而且有助于为行业专家建立有效的沟通平台,用于解决问题。标准使低成本、易消费的技术成为可能。

区块链供应商提供各种专业化服务,包括:

  • 变体信任系统:共识、挖掘、工作证明等。

  • 锁定到单一信任系统

  • 专门为特定用例构建的基础设施组件

  • 通过概念验证的经过实地测试的设计

供应商不遵循基于标准化技术集的参考架构的技术风险是企业的碎片化区块链模型。

从业务角度来看,基于开放标准的区块链方法提供了灵活性,以及可插拔和模块化的信任系统,因此是最理想的选择。这种方法使企业能够接受专门的区块链,如 Ripple,为信任系统提供一个配置层,并提供一个具备支持其技术的独立业务领域。

选择区块链框架的技术考虑

当组织考虑区块链的技术影响时,他们应该从这样一个前提出发,即它不仅仅是另一个应用程序。它是一个生产网络,涉及风险和成本以确保正确的维护和维护。

在评估区块链技术影响时,以下是一些需要考虑的重要事项。

身份管理

身份管理是一个复杂而涉及的主题,特别是在受监管的行业,其中身份必须被管理并具有重大的业务后果,例如围绕了解客户(KYC)、反洗钱(AML)和其他报告和分析功能的活动:

  • 权限控制 是**成员注册证书(eCerts)和每个成员的交易证书(tCerts)**的概念;这些使实体能够获得权限和识别,同时完成交易。

  • 终端用户身份,由参与区块链网络的实体维护,是 LDAP/用户注册表到 tCerts 或交易 ID 的映射,以进行追踪(了解客户,以及了解客户的客户)

其他身份管理考虑因素包括:

  • LDAP 或现有的用户注册表不会消失,必须考虑为设计重点,因为成熟的身份验证和授权系统通常已经投入了大量投资和安全策略。

  • 信任系统是区块链技术的核心,并且必须为需要交易可追溯性的用例打开信任之路。

  • 区块链的身份和用于区块链的身份

  • 身份获取、审查和生命周期

  • 与基于用例的信任系统对齐

可扩展性

要考虑可扩展性,因为下游交易系统可能会影响关键的业务系统,这既是商业考虑,也是技术考虑。比如针对可扩展性的技术选择,例如用于共享账本的数据库选择、相邻系统集成、加密和共识,都会导致可以适应网络成员或交易增长的可预测成本的系统设计。

企业安全

有三个层面的企业安全需要考虑:

  • 物理 IT 基础设施层,其中包括特定用例的问题,如 EAL5、网络和基础设施隔离要求。

  • 区块链中间件层,其中包括加密模块要求、加密级别、数据存储、传输和数据静态加密、以及网络参与者之间数据的可见性。

  • 区块链共识(信任系统层),是区块链的核心,并且是保证基本数据存储特性的必要条件。如果网络中有更多参与者,他们必须带来资本以实现规模化。这就是关于在较低准入门槛下建立符合企业数据质量的共享数据存储的问题。共识,即使是最小的共识,在现有的架构中也是必要的。加密货币基础的信任系统和非加密货币基础的信任系统现在存在分歧。前者的模型,如 POW/PoS,不适合企业用例,希望创建权限区块链。

开发工具

开发工具选择包括集成开发环境,业务建模和模型驱动开发。

加密经济模型

加密经济模型指的是使用公钥密码学进行身份验证和经济激励来保证系统不会倒退或发生其他修改的去中心化系统。要充分理解区块链的概念和计算机科学中加密的好处,我们必须首先理解去中心化共识的概念,因为这是基于加密的计算革命的关键。

有系统性治理的去中心化

旧的范式是中心化的共识,其中一个中央数据库将决定交易的有效性。分散化方案打破了这一格局,将权威和信任转移给了分散化网络,并使其节点能够持续和顺序记录交易在公共块上,创建唯一的链—因此得名区块链。通过哈希码的密码学保证了交易来源的身份验证,消除了中央中介的需要。通过结合密码学和区块链,该系统确保没有重复记录相同的交易。

区块链系统设计应保留去中心化数字交易处理的理念,将其调整为一个许可网络,同时根据企业环境的需要集中一些监管合规和维护活动。

企业支持

企业支持区块链的重要性与重新考虑估算工作的原因相同。请记住,区块链不应被视为另一个应用程序。它是一个涉及风险和维护成本的生产网络,并且不能简单地使用现有的开发、基础设施和服务。

基于用例驱动的可插拔选择

为了确保您的区块链解决方案能够允许基于用例的可插拔选择,请考虑以下问题。

共享账本技术

您尝试通过区块链解决的用例、设计要求和问题都将有助于确定共享账本和数据库技术的选择。

共识

共识指导了信任系统并推动了区块链应用基础设施的技术投资,因此它是区块链的核心。此外,并不存在适用于所有用例的共识类型。用例定义了参与者之间的交互,并通过共识模型建议了最适合的信任系统。

共识是验证区块链网络上的网络请求或交易(部署和调用)顺序的一种方式。正确排序网络交易至关重要,因为许多交易依赖于一个或多个先前交易(例如,账户借方经常依赖于先前的贷方)。

在区块链网络中,没有单一的权威确定交易顺序;相反,每个区块链节点(或对等方)都有平等的发言权,通过实施网络共识协议来建立顺序。因此,共识确保了节点的多数同意了交易附加到共享账本的顺序。共识通过解决提出的交易顺序中的不一致来确保所有网络节点都在同一区块链上运行。换句话说,它确保了区块链网络中交易的完整性和一致性。

加密算法和加密技术

选择区块链系统设计可能受加密库和加密技术的指导。 组织的用例需求将决定这种选择,并推动区块链应用基础设施的技术投资:

  • 非对称: RSA(1024-8192),DSA(1024-3072),Diffie-Hellman,KCDSA,椭圆曲线密码学(ECDSA,ECDH,ECIES)与命名、用户定义和 brainpool 曲线

  • 对称: AES,RC2,RC4,RC5,CAST,DES,三重 DES,ARIA,SEED

  • 哈希/消息摘要/HMAC: SHA-1,SHA-2(224-512),SSL3-MD5-MAC,SSL3-SHA-1-MAC,SM3

  • 随机数生成:FIPS 140-2 批准的 DRBG(SP 800-90 CTR 模式)

用例驱动的可插拔选择

正如先前所述,用例将定义参与者之间的互动,并建议使用共识模型选择最适合的信任系统。

企业集成和设计可扩展性

设计区块链网络与组织中现有的记录系统并存是一个重要的成本考虑。 集成应通过业务和技术问题,因为下游交易系统会影响重要的业务系统。 通过与许多企业合作,我发现将区块链与相邻系统集成对其区块链项目的成本影响很大。 它确实需要在规划阶段早期解决,以免对企业采用产生不利影响。

想到运营问题也很重要。 通过保护贸易、信任和所有权的要素以及区块链的固有属性(如不可变性、出处和共识),信任系统承诺有助于消除冗余和重复的系统和流程。 这些重复成本组织大量资源,导致交易处理较慢和相关的机会成本。 区块链采用的一个目标应该是解决现有流程的核心痛点。 期望是一个透明的分类账,增加信任,节省时间和重大成本,并提供更好的客户服务。

对于网络可扩展性,设计可扩展性意味着在规划实施时考虑未来的增长。 扩展性衡量了系统的扩展能力以及实施扩展所需的工作量。 可扩展性在区块链业务网络设计中非常重要,不仅要适应业务的动态特性(包括所有的规定、竞争压力和市场动态),还要适应网络的增长(监管机构、市场制造商、扰乱、服务提供商等的增加)。

以下是一些设计考虑,以确保网络的可扩展性:

  • 成员灵活性:区块链网络可能从一个有限的参与者和角色群体开始,但后来可能会有新的参与者想要加入网络,而其他人可能想要离开。因此,您必须考虑成员变更的机制,包括对(共享)数据的访问。在设计可扩展性时,成员类型也是一个重要的考虑因素,因为成员的角色和类型可能随时间而变化。

  • 计算权益:基于加密货币的信任系统和基于计算权益的信任系统之间存在分歧,因此这是一个相当新的概念。参与者类型及其在网络中的业务利益类型是长期可持续的基础设施成本和维护的决定因素。例如,监管机构的成本模型可能与区块链驱动的业务网络的主要受益者的成本模型大不相同。

  • 共同的业务利益:区块链网络为企业提供了特定的优势,如降低风险、可靠且可预测的交易网络、较低的合规成本等。但这些共同利益可能导致其他运营问题,例如在实体加入和离开网络时的数据共享和所有权。由于围绕数据所有权的法规不断发展,以及行业对数据持久性的要求,因此在设计区块链系统时应仔细评估这些问题。

  • 治理:治理包括管理技术工件,如技术基础设施,并管理区块链网络中的数据和智能合约。建议在以下类别中分层治理:

    • 区块链网络/技术治理

    • 区块链数据治理

    • 区块链智能合约治理

    • 区块链交易管理治理

在设计可扩展性时,目标应该是确保区块链网络具有可持续的运营要素和业务增长要素。例如,在可持续模型中,每个参与者都可以部署管理其自身业务流程的链码,同时还可以控制变更业务流程、政策和监管要求。

其他考虑因素

除了上述提到的方面外,还有一些其他注意事项需要牢记。它们在以下部分简要解释。

共识、ACID 属性和 CAP

一致性模型永远不会降为 0,因为当 NoSQL 成为标准时,各种 NoSQL 系统通过理解 CAP 定理解决了它们的问题,而 RDBMS 企业社区则坚持了他们的 ACID 属性。区块链很可能提供了打破 CAP 并保持 ACID 的基本元素。以下是一些想法。

CAP

Cap 代表:

  • C—一致性:共识确保了发生的事情以及发生的顺序只有一个真相。

  • A—可用性:区块链的所有调用都是异步的,这意味着调用应用程序在确保一致性和持久性的同时可以取得进展(链接也保证了这一点)

  • P—网络分区:再次,共识防止了在网络分区后合并时出现冲突的分裂大脑

ACID

ACID 代表:

  • A—原子性:链码编程模型具有全有或全无的行为,允许你将活动分组在一起。要么所有事情发生,要么就什么都不发生。

  • C—一致性:我们相信 NoSQL 的新世界在这方面做得不好。我相信这意味着与 CAP 中的 C 相同。

  • I—隔离性:隔离性表示两个事务是串行的,这正是区块构造和链接所做的。

  • D—持久性:网络上的链接和复制确保如果一个或多个节点宕机,数据不会丢失。这就是为什么每个人都想带一个节点,以及为什么这些节点不应该不共存。

验证 – SSCs 签名和加密

安全服务容器(SSCs)中,软件、操作系统、虚拟化程序和 Docker 容器映像都不能被修改。证书可以包含在 SSC 中,以便它们可以自我验证为远程方的真实性。例如,在构建 SSC 时包含 SSL 证书有助于确保你正在与真实实例通话,因为 SSL 证书始终保护(加密)在 SSC 中。

使用 HSM

根据Wikipedia硬件安全模块(HSM)是一种物理计算设备,用于保护和管理用于强身份验证的数字密钥并提供加密处理。这些模块通常以插件卡或直接连接到计算机或网络服务器的外部设备的形式出现。

管理高安全性设备(如 HSM)可能是一个真正的挑战,因为与足够的安全性和控制相关。事实上,今天的标准规定了 HSM 管理(和密钥管理)系统的某些安全方法和级别。

摘要

在企业中采用区块链将需要权衡考虑。组织不仅需要运行、管理和维护其现有基础设施;它们还需要为这个承诺带来转型的新计算模型铺平道路。

在受监管的行业中,组织可能会面临合规成本的双重影响,因为即使是新技术平台也需要遵循已建立的监管框架和经过验证的技术架构标准和设计。考虑采用分层防御的理性方法,结合多种减轻安全风险的安全控制,有助于保护其资源和数据。采用分层防御方法,数字资产/智能合约以及分类数据将受到保护。

第二章:探索 Hyperledger Fabric

本章重点讨论 Hyperledger Fabric 项目——其组件、设计、参考架构以及企业就绪性。我们还将讨论由Linux FoundationLF)托管的 Hyperledger 项目的整体目标以及开源和开放标准的重要性。目标是建立对各种 Hyperledger 项目的多样性以及哪些框架和工具可能适合特定企业用例和软件消费模型的理解。尽管区块链技术领域不断变化,但 Hyperledger 项目代表着一种支持成熟和经过同行评审的技术结构,旨在供企业使用,并由多样化的人才和社区利益推动。

本章将涵盖以下主题:

  • Hyperledger 框架、工具和构建模块

  • Hyperledger Fabric 组件设计

  • Hyperledger Fabric——一笔交易的旅程

  • 探索 Hyperledger Fabric

  • 理解由区块链驱动的企业网络治理

Hyperledger 框架、工具和构建模块

现在我们已经看过 Hyperledger 在开放计算运动中的基础,以及它对行业的好处,让我们谈谈 Hyperledger 的框架、工具和构建模块。

Hyperledger 框架

以下是五个区块链框架:

  • Hyperledger Iroha:Iroha 专为移动开发项目设计,基于 Hyperledger Fabric,并由 Soramitsu、Hitachi、NTT Data 和 Colu 贡献。它具有现代的、面向领域驱动的 C++设计,以及一种名为Sumeragi的基于新链式拜占庭容错共识算法。

  • Hyperledger Sawtooth:Sawtooth 由 Intel 贡献,包括 Intel 提出的一种称为经过时间证明PoET)的新颖共识算法。 PoET 旨在尽可能有效地实现分布式共识。Hyperledger Sawtooth 在许多领域具有潜力,支持许可和无许可部署,并认识到各种需求。Sawtooth 被设计为多功能的。

  • Hyperledger Burrow:Hyperledger Burrow 最初由 Monax 和 Intel 贡献,是一个模块化的区块链,客户端构建了符合以太坊虚拟机EVM)规范的区块链。

  • Hyperledger FabricHLF):由 IBM 贡献的 Hyperledger Fabric 旨在成为以模块化架构开发应用程序或解决方案的基础。它允许插拔式组件,例如共识和成员服务,并利用容器托管称为链码的智能合约,构成系统的应用逻辑。本章余下部分将专注于 Hyperledger Fabric 及其设计、组件、架构和整体企业设计。

  • Hyperledger Indy:最初由 Sovrin 基金会贡献,Indy 是一个支持分布式账本上独立身份的 Hyperledger 项目。Hyperledger Indy 提供了工具、库和可重复使用的组件,用于提供基于区块链或其他分布式账本的数字身份:

区块链工具

目前 Hyperledger 项目中还有五种工具,全部由 LF 托管。这些工具如下:

  • Hyperledger explorer:Hyperledger explorer 最初由 IBM、英特尔和 DTCC 贡献,可以查看、调用、部署或查询区块、交易和相关数据、网络信息(名称、状态、节点列表)、链码和交易族,以及存储在账本中的其他相关信息。

  • Hyperledger cello:Cello 也由 IBM 贡献。它旨在将按需即服务部署模型引入区块链生态系统,以减少创建、管理和终止区块链所需的工作量。Cello 可以在各种基础设施上高效、自动地提供多租户链服务,例如裸金属、虚拟机和其他容器平台。

  • Hyperledger composer:Hyperledger composer(由 IBM 和 Oxchains 贡献)是一组用于构建区块链业务网络的协作工具,可加速智能合约和区块链应用程序的开发,以及它们在分布式账本上的部署。

  • Hyperledger quilt:Hyperledger quilt 来自 NTT 数据和 Ripple,是 Ripple 的跨账本协议的 Java 实现,旨在在分布式和非分布式账本之间传输价值。

  • Hyperledger caliper:Caliper 是一个区块链基准测试工具,允许用户使用预定义的用例来衡量特定实现的性能,目前处于孵化状态,由众多组织的开发人员贡献。

区块链解决方案的构建基块

如第一章所述,区块链 - 企业和行业视角,区块链承诺在金融服务、供应链、物流和医疗保健等行业根本解决time信任问题。它旨在简化业务流程,从而解决低效率问题。它是基于信任、责任和透明度构建的新一代交易应用程序的技术。每个工业区块链都有几个共享特征,包括以下内容:

  • 共享的真实唯一数据源

  • 安全且防篡改

  • 私密不可链接身份

  • 可扩展的架构

  • 机密性

  • 可审计的

下图将这些特点总结为四个原则:

区块链解决方案由四个基本构件组成——共享账本、隐私、信任和智能合约。让我稍微详细解释一下这些构件:

  • 共享账本:通过比特币区块链,其目的是实现可见性的民主化;然而,企业区块链由于消费者数据的监管而需要采用不同的方法。只追加不可变分布式事务记录可以通过 SQL 或无 SQL 分布式数据库来实现。

  • 加密保障隐私:通过加密保障隐私对于确保交易经过认证和验证至关重要。在区块链设计中加入加密技术是至关重要的,以加强安全性并增加突破分布式系统的难度。当您在使用较少民主化或经过许可的账本网络时,有关加密的考虑会发生变化。

  • 信任系统或共识:信任意味着利用网络的力量来验证交易。在任何区块链系统或应用中,信任都是至关重要的,我更喜欢使用信任系统而不是共识系统这一术语,因为信任是决定利益相关者投资于任何区块链基础设施的基础元素。每当新的参与者进入区块链领域并将区块链技术应用于新的用例或专业领域时,信任系统就会发生改变。信任模型真正是区块链的核心——它是传递信任交易所有权原则的基础。信任是使区块链能够取代交易系统的关键,但这只有在分布式/共享账本解决了交易和所有权时才能实现。对于各种用例,仍然需要大量工作来定义优化的信任系统。数据库解决方案正在努力解决规模和移动用例的问题,但在点对点和共享经济模型以及 B2B 模型方面还需要更多的工作。

  • 智能合约:在区块链的背景下,智能合约是嵌入到交易数据库中并随交易执行的商业协议。在业务中需要规则来定义价值流动和交易状态,这就是合同在这里的功能。合同之所以智能,是因为它是一个计算机协议,用来执行合同的条款。各种合同条款(如抵押、担保、财产权利的划定等)可以被编码,以强制执行对合同条款的遵守,并确保交易成功——这是智能合约背后的基本理念。智能合约的设计目的之一是让一方放心另一方将履行他们的承诺。这类合同的目标之一是降低验证和执行成本。智能合约必须是可观察的(意味着参与者可以看到或证明彼此的与合同相关的行动)、可验证的(意味着参与者可以向其他节点证明合同是否已执行或违约)、以及私密的(意味着合同内容/执行的了解应仅涉及执行它所需的必要参与者)。比特币为智能合约提供了相关规定;然而,它缺乏图灵完备性、缺乏状态等能力。以太坊通过构建一个内置图灵完备编程语言的区块链,改进了比特币的局限,因此任何人都可以编写智能合约和去中心化应用,创造自己的所有权、交易格式和状态转换函数的任意规则。这些进步使得复杂的合同可以在区块链中被编码,比如在飞行延误超过一定时间后即时向旅行者的银行账户转移信用,或者在实现绩效目标时支付员工补偿等。

    这在实际中如何工作?嗯,智能合约被部署为区块链节点上的代码,我们可能更恰当地称之为智能合约代码。这个代码是利用区块链技术来补充或替代现有的法律合同。这个智能合约代码是用 Solidity 或 Golang 等编程语言部署在区块链节点上的。在区块链上部署代码提供了三个重要的属性:

    • 区块链继承的永久性和抗审查性,

    • 程序本身控制区块链资产的能力,比如在参与者之间转移所有权或资产数量

    • 程序由区块链执行,确保始终按原样执行,没有人可以干扰

在企业世界中,智能合约可能涉及区块链的智能合约代码,以及更传统的法律合同。例如,智能合约代码可能在土地登记区块链网络上执行,将房屋所有权从一方转移到另一方,以便房屋登记记录实时更新,并且所有参与者(如城市、房地产经纪人、律师和银行)都可以在销售完成后更新自己的记录。但是,购房者将坚持要求具有补偿条款的法律合同,以 Cover any undiscovered liens。

Hyperledger Fabric 组件设计

让我们讨论一下促进区块链技术原则的各种组件,例如共享账本、加密、信任系统和智能合约。 这些组件代表了 Hyperledger Fabric 的基础设施组件,并且提供了与链码或智能合约开发构造的隔离。链码或智能合约开发细节将在单独的章节中详细讨论。

以下图示了 Hyperledger Fabric 基础设施组件:

Hyperledger Fabric 基础设施组件

以下是基础设施组件:

  • Hyperledger Fabric CA 是成员服务的一个实现,但不是必须使用的(即,任何可发行 EC 证书的基于 X509 的 PKI 基础设施都可以使用)

  • 专用订购节点

    • 实现原子广播 API

    • 订购并批处理交易,并对每个批次(区块)进行签名以创建哈希链

    • Hyperledger Fabric 提供了两种实现——Solo(用于开发/测试)和基于 Kafka 的实现用于生产/容错

    • 订购服务是可插拔的——实施者只需根据 gRPC 接口定义提供原子广播 API

  • 节点现在负责现有的智能逻辑(链码)和维护分类帐

    • 背书模拟交易(即执行交易,但不提交交易)

    • 节点从订购节点接收批量背书的交易,然后验证和提交交易(这消除了不确定性)

Hyperledger 设计原则

再次强调,Hyperledger Fabric 是设计用于部署模块化和可扩展架构的区块链实现。它具有模块化子系统设计,以便随着时间的推移可以插入和实施不同的实现。本节介绍了 Hyperledger Fabric 参考架构,并描述了各种组件/模块及其交互和功能的详细信息。理解参考架构有助于更好地进行解决方案和技术设计决策,特别是在可扩展性、安全性和性能方面。

在本书中,我们将讨论 Hyperledger Fabric 的参考架构,请注意所有 Hyperledger 项目(先前提到的框架)遵循包括以下原则的设计理念:

  • 模块化和可扩展的方法:这意味着所有框架的所有组件都具有模块化。Hyperledger 为所有项目定义的组件包括(但不限于)以下内容:

    • 共识层

    • 智能合约(链码)层

    • 通信(八卦)层

    • 数据存储(持久性、日志和分类账数据)

    • 身份服务(信任根——用于识别参与者)

    • API

    • 可插拔加密

  • 互操作性:这一原则是关于向后互操作性,而不是关于各种 Hyperledger 项目驱动的区块链系统或业务网络之间的互操作性。

  • 专注于安全解决方案:企业及其业务网络的安全至关重要,因此专注于安全——不仅仅是加密抽象本身,而是组件之间的交互以及管理权限化区块链的结构。大多数开始使用权限化区块链的行业都是成熟且受监管的行业。

  • 代币(或硬币或加密资产)不可知的方法:这在治理部分进行了详细讨论,但 Hyperledger 项目不使用加密资产、加密货币、代币或类似硬币的构造作为建立信任系统的激励机制。虽然有一种资产代币化的概念,表示物理、虚拟或非物质化的资产,但资产代币化是一个与系统中生成的系统性代币截然不同的概念,后者是作为激励经济学的虚拟化而在系统中生成的。

  • 专注于丰富且易于使用的 API:这里的重点是确保区块链系统不仅具有企业中间件访问权限,还能够访问业务网络、现有参与者和新系统,而不暴露区块链驱动的业务网络的细节。

CAP 定理

2000 年在 ACM 分布式计算原理研讨会(PODC)上由埃里克·布鲁尔(Eric Brewer)提出的 CAP 定理(dl.acm.org/citation.cfm?id=343502)指出,在分布式数据存储中,不可能保证以下三个属性中的超过两个:一致性(C)、可用性(A)和分区容错性(P)。因此,分布式数据存储可以根据它保证的两个属性来描述,即 CA、CP 或 AP。

更具体地说,该定理旨在分布式系统部署在不可靠网络(例如存在故障和延迟的网络,如互联网)中,导致系统组件的分区。根据 CAP,在这些环境中,系统设计必须专注于可用性和一致性之间的平衡。例如,关系型数据库管理系统(RDBMS)通常提供的 ACID(原子性、一致性、隔离性、持久性)方法保证了单个节点上的一致性,但以牺牲跨多个节点的可用性(CP 类型系统)为代价。然而,需要注意的是,不同的配置可能会产生不同的组合,即 CA 或 AP。

相反,Fabric 与许多其他区块链平台一样,设计为 AP 类型系统,同时也采用了最终一致性,也被称为 BASE(基本可用、软状态、最终一致性)。

在区块链背景下,CAP 属性可以定义如下:

  • **一致性:**区块链网络避免了分类账的任何分叉

  • **可用性:**客户端提交的交易将永久写入分类账,并可在所有网络节点上使用。

  • **分区容错性:**即使区块链网络中出现任意数量的交易提案或区块在节点之间的物理网络介质中被丢弃(或延迟),网络仍然可以继续运行。

Fabric 实现了以下 CAP 属性:

  • **一致性:**通过使用 MVCC 对交易进行总序,并使用版本控制实现。

  • **可用性:**通过在每个节点上托管分类账的副本。

  • **分区容错性:**即使节点失败(达到阈值),也要保持运行。

如您所见,大多数区块链系统默认情况下保证了可用性和分区容错性(CAP 定理的 AP 属性)。然而,一致性更难提供。

Fabric 通过结合以下元素实现了一致性:

  • 交易处理被分割成一系列步骤,并跨网络的多个组件执行。

  • 客户端连接到通信渠道,并向背书节点提交交易提案,然后提交给排序服务。

  • 排序服务将交易按照总序排序成区块,即保证了整个网络上的交易顺序一致。一旦创建了区块,就会广播到通道的每个成员节点。广播协议保证了将区块以正确的顺序可靠地传递给节点,即总序广播。

  • 正如我们将在多版本并发控制中解释的那样,对等节点在接收到区块后,使用 MVCC 根据事务读取集中存储的键版本来验证每个事务。MVCC 验证保证了结果分类帐和世界状态的一致性,并防止了诸如双重支付之类的攻击。然而,它也可能导致被提交但顺序违反ReadSet版本验证检查的有效事务被消除。然后,在分类帐中将事务标记为有效或无效。

  • 然后,分类帐包含一系列完全有序的区块,其中每个区块包含一系列完全有序的事务(有效或无效),从而形成了对所有事务强制实施总顺序的分类帐。

Hyperledger Fabric 参考架构

Hyperledger Fabric 遵循模块化设计,以下是一些可能的组件或模块,可以插入和实现。请注意,此列表并不详尽:

  • 成员服务:此模块本质上是一个许可模块,并在网络创建过程中建立信任根,但这也是确保和管理成员身份的关键。成员服务本质上是一个证书颁发机构,同时利用了公钥基础设施PKI)的元素,用于诸如密钥分发、管理和建立随着网络增长而形成的联邦信任等方面。成员服务模块为区块链网络的成员发放证书提供了一个专用的数字证书颁发机构,并利用 Hyperledger Fabric 提供的加密功能。

  • 事务:事务是向区块链发出的执行分类帐上功能的请求。该功能由链码实现。通过将事务链接到先前的区块,并确保交易完整性(如果受保护),通过链接先前链接的区块中的加密信息或哈希,加密确保了交易的完整性。Hyperledger Fabric 中的每个通道都是其自己的区块链。

  • 智能合约或链码服务:链码是存储在分类帐上作为事务的一部分的应用级代码。链码运行可能修改世界状态的事务。事务逻辑以链码形式编写(使用 Go 或 JavaScript 语言),并在安全的 Docker 容器中执行。事务通过链码作用的通道上的数据进行转换。

这里是由链码服务启用的智能合约或链码元素。链码安装在对等节点上,需要访问资产状态以执行读取和写入操作。然后,链码在特定通道上针对特定对等节点进行实例化。通道中的分类帐可以在整个对等节点网络中共享,也可以仅包括特定的参与者集。对等节点能够参与多个通道:

  • 事件:验证对等方和链码的过程可能会在网络上产生事件(预定义事件和由链码生成的自定义事件),应用程序可以监听这些事件并采取行动。这些事件由事件适配器消耗,事件适配器可能使用 WebHooks 或 Kafka 等工具进一步传递事件。Fabric 提交对等方提供事件流以向已注册的监听器发布事件。截至 v1.0,唯一发布的事件是块事件。每当提交对等方向账本添加验证的区块时,都会发布一个块事件:

  • 共识:共识是任何区块链系统的核心。它也实现了信任系统。一般来说,共识服务使得网络成员可以提出并验证数字签名的交易。在 Hyperledger Fabric 中,共识是可插拔的,并与 Hyperledger 提出的背书-排序-验证模型紧密关联。Hyperledger Fabric 中的排序服务代表共识系统。排序服务将多个交易打包成区块,并输出包含交易的哈希链接的区块序列。

  • 账本:另一个组件是分布式加密账本,包括只追加数据存储。这提供了在分布式账本上查询和写入数据的能力。有两个选择:

    • Level DB(默认嵌入式 KV 数据库)支持按键查询、复合键查询和键范围查询

    • Couch DB(外部选项)支持按键查询、复合键查询、键范围查询以及完整的数据丰富查询

  • 客户端 SDK:客户端 SDK 可以创建部署和调用共享账本上交易的应用程序。Hyperledger Fabric 参考架构支持 Node.js 和 Java SDK。软件开发工具包类似于编程工具包或一组工具,提供给开发人员编写和测试链码应用程序的库环境。SDK 在区块链应用程序开发中至关重要,并将在后续章节中进行详细讨论。SDK 包含的特定功能包括应用程序客户端、链码、用户、事件和加密套件。

Hyperledger Fabric 运行时架构

现在我们已经查看了参考架构,让我们考虑一下 Hyperledger Fabric 的运行时架构:

以下概述了 Hyperledger Fabric 运行时交易处理流程:

  • 交易提案(应用程序 SDK)

    1. 交易提案由应用程序 SDK 提交

    2. 它会收到交易提案响应(包括 ReadWrite 集)后的认可。

    3. 它将交易(包括 ReadWrite 集)提交给排序服务

  • 交易背书

    1. 交易被发送到其通道上代表背书对等方的交易对手

    2. 每个对等方通过调用指定的链码函数执行交易并签署结果,该结果成为交易的读写集

    3. 每个对等方可以参与多个通道,允许并发执行

  • 交易提交给排序服务

    1. 排序服务接受认可的交易,并根据插件共识算法对其进行排序,然后将其传递到通道上

    2. 通道上的对等方接收交易并在提交到分类账之前进行验证

  • 交易验证

    1. 验证每个交易并提交区块

    2. 验证认可策略

    3. 验证状态数据库中的 ReadSet 版本

    4. 将区块提交到区块链

    5. 将有效交易提交到状态数据库

分解设计的优势和优点

Hyperledger Fabric 的组件设计提供了几个优势。这些优势中的许多与业务网络治理有关,对于企业中的 Hyperledger Fabric 来说,这是一项重要的合规性和成本考虑。

这些好处包括以下内容:

  • 将开发设计与运行时设计区分开:分离开发和运行时设计很重要,因为这种区分对开发最佳实践和基础设施/混合云变体很重要,并确保遵守当前企业及其与业务网络应用开发的连接以及 DevOps 实践的连接。

  • 区分设计要求和基础设施/部署能力:组件化设计使我们能够将基础设施设计(包括网络连接、安全性、许可和合同工具等)与业务网络蓝图的整体应用设计分开,后者决定了技术蓝图。

  • 整合网络设计原则:Hyperledger Fabric 的模块化设计可以解决基础设施扩展问题,如连接数、协同位置、安全性、容器部署实践等。在网络设计方面有各种考虑因素,如云部署、混合和/或本地部署,以及任何可用选项的组合,这取决于业务网络中各个成员的需求。网络设计还解决了网络增长及其导致的性能和安全驱动的服务级别协议SLA)对其成员的挑战。

  • 解决频道设计原则:模块化或组件化设计也可以解决参与者之间的隔离、数据隐私和机密性,以及提供强大的审计能力的受控/许可访问。Hyperledger Fabric 中的频道结构使我们能够满足业务蓝图需求,实施可能是双边、三边甚至多边的业务定义交易。频道还提供了一种途径,限制交易数据的可见性仅限于少数参与者,或者在需要时提供全面访问权限,例如监管机构。频道设计还解决了交易处理、数据可见性、业务规则执行等关键业务需求。它还涉及技术影响,如可扩展性、安全性以及支持业务网络的基础设施成本。最后,频道设计解决了网络增长带来的业务挑战,以及为成员提供的性能和安全性驱动的服务水平协议。

  • 采用 Hyperledger Fabric Composer 模型驱动开发:Hyperledger Composer 是之前在 Hyperledger 工具中讨论的工具之一,为模块化开发提供了一种途径,使用便携、标准化的方式增加治理和控制,类似于 JEE 结构,如 JAR/WAR/RAR 等。业务网络存档BNA)是一种存档,可集成到 DevOps 实践中,用于跨企业团队开发和协作的生命周期管理能力。其理念是将链码开发与基础设施设计分开,将维护企业或业务网络应用程序技术实践这两个方面所需的能力分开。有关 Hyperledger Fabric Composer 的更多详细信息将在专门讨论 Composer 和工具的单独章节中介绍。

上述组件化设计的每一个优势在运行时/基础设施设计方面都有成本影响(即资源的使用和由此产生的成本)、灵活设计(例如产品和关系的变化)和解决方案的持久性(包括全球企业云基础设施的全球足迹,包括通过维护和支持形式的技术和业务专家的可靠访问)——所有这些对合规、治理和解决方案的持久性,以及由区块链驱动的业务网络至关重要。

Hyperledger Fabric——示例交易的过程

现在,让我们来看看使用 Hyperledger Fabric 的示例交易的过程。本节将帮助奠定 Hyperledger Fabric 概念和组件的基础,以便更好地理解交易处理所涉及的各个层面:

Hyperledger Fabric 演练

Fabric 引入了一种新设计的区块链,保留了交易处理架构,旨在实现安全、可扩展、弹性、模块化和保密的设计。Hyperledger Fabric(在撰写本书时,当前版本为 1.1)支持执行支持企业友好编程模型的分布式应用程序。Hyperledger Fabric 中的组件具有模块化设计,非常适合由各种企业组成的商业网络。Hyperledger Fabric 引入了基于三个步骤的模型,一个认可-排序-验证架构,旨在在不受信任的环境中执行不受信任代码的分布式执行。这种分离不仅允许规模的供应,还通过在每一层进行分离来确保安全性。

交易流程分为三个步骤,可以在系统中的不同实体上运行:

  1. 对交易的认可和检查其有效性验证步骤):这一步包括频道成员检查和遵守认可政策,这些政策定义了验证交易提案的可接受的协商方式。由于对等方需要更新分类帐(在交易确定性之后),因此订阅频道的对等方会审核提案并提供其分类帐版本的(R)读取和(W)写入集。这一验证步骤非常重要,因为它为交易验证提供了第一步。这一检查也起到了门禁的作用,防止了交易的计算错误下游处理,这可能具有计算成本昂贵的风险。

  2. 通过排序服务进行排序:这是一种共识协议,旨在可插拔,不考虑交易语义。共识的可插拔性为企业和商业网络提供了巨大的灵活性,因为有各种行业、用例和网络参与者之间的交互的共识机制考虑因素。

  3. 验证或交易提交:这意味着提交交易,因此需要按照应用程序特定的信任假设进行一系列最终验证。

Hyperledger Fabric 交易涉及三种类型的节点:

  • 提交对等方是维护分类帐和状态的节点。提交对等方是提交交易并可能持有智能合约或链代码的一方。

  • 认可对等方是一个专业的提交对等方,可以批准或否决交易提案的认可。认可对等方必须持有智能合约。

  • 排序节点(服务)与提交对等方节点进行通信;它们的主要功能是批准交易块被纳入分类帐。与提交对等方和认可对等方不同,排序节点不持有智能合约或分类帐。

验证可以分为两种角色,认可和排序:

  • 签署交易意味着验证它是否遵守智能合约;背书人签署合同以完成这个验证方面

  • 订购验证要包含到分类帐中的交易;这种形式的验证有助于控制录入分类帐的内容并确保其一致性

那么链码调用呢?在 Hyperledger Fabric 交易中,模拟(链码执行)和区块验证/提交是分开的。

执行链码操作(换句话说,进行业务交易)涉及三个阶段;

  1. 第一阶段是通过背书对等方的模拟执行链码操作。可以启用背书者的并行模拟以帮助提高并发性和可伸缩性,因为模拟不会更新区块链状态。

  2. 接下来,模拟确定业务交易提案,即读取集/写入集,并将其广播到订购服务。

  3. 然后,交易提案根据其他交易进行排序,并广播到提交对等方(包括背书对等方),他们验证其读取集在模拟后未被修改,并自动应用其写入集。

通道也是交易过程中的一个重要方面,因为对等方通过通道以共识方式交换消息,并确保不同分类帐之间的隐私。以下是关于通道的一些注意事项:

  • 所有节点都不必连接到它们

  • 对等方通过访问控制策略连接到通道

  • 订购服务对通道广播的交易进行排序

  • 对等方按通道以完全相同的顺序接收交易

  • 交易以加密链接的区块形式交付

  • 每个对等方都会验证交付的区块并将其提交到分类帐

探索 Hyperledger Fabric

区块链网络中的参与者:区块链是一个基于网络的基础设施,其中应用网络中心化的设计、开发、部署、管理和支持构建。因此,了解与区块链网络进行交互以进行管理、支持、业务用户、监管等各种目的的各种参与者及其角色非常重要:

每个参与者都有一个角色和入口点,并定义了一个有助于网络治理、审计和合规要求的治理结构。业务网络治理(以下点中详细介绍)是一项重要的合规和成本考虑因素。用户是区块链的用户方。他们创建和分发区块链应用,并使用区块链执行操作。这些参与者是一致的,并基于 ISO/IEC 17788 的云计算参与者和角色:

  • 开发人员:区块链开发人员是为用户(客户端)创建应用程序的参与者,并开发与区块链交互的智能合约(服务器端),然后由区块链用户使用以启动交易。他们还编写代码以使区块链能够与传统应用程序交互。

  • 管理员:区块链管理员执行管理活动,如部署和配置区块链网络或应用程序。

  • 运营商:区块链运营商负责定义、创建、管理和监控区块链网络和应用。

  • 审计员:区块链审计员负责审查区块链交易,并从业务、法律、审计和合规性的角度验证其完整性。

  • 业务用户:此术语指在业务网络中操作的用户。他们使用应用程序与区块链交互,但可能不知道区块链,因为它将是一个不可见的交易系统。

区块链网络中的组件

一般来说,区块链系统由许多节点组成,每个节点都有分类帐的本地副本。在大多数系统中,节点属于不同的组织。节点彼此通信以就分类帐内容达成一致。

获得这种一致性的过程称为共识,为此已经开发了许多不同的算法。用户向区块链发送交易请求以执行链设计的操作。一旦交易完成,交易记录将被添加到一个或多个分类帐中,并且永远不能被更改或删除。区块链的这种特性称为不可变性。密码学用于保护区块链本身以及区块链系统的各个元素之间的通信。它确保分类帐不能被更改,除非添加新的交易。密码学确保来自用户或节点之间的消息的完整性,并确保操作仅由授权实体执行:

在区块链上执行交易的权限可以采用两种模式之一:有权限或无权限。在有权限的区块链中,用户必须在允许执行交易前加入区块链。加入过程会给予用户凭证,这些凭证在用户执行交易时用于标识用户。在无权限的区块链中,任何人都可以执行交易,但通常被限制只能对自己的数据执行操作。区块链所有者开发了一个可执行的软件模块称为智能合约,它被安装到区块链中。当用户向区块链发送交易时,它可以调用智能合约模块,该模块执行由智能合约模块创建者定义的功能。

开发人员互动

Hyperledger Fabric Explored章节的介绍中所述,区块链开发人员可以担任多种角色,包括为用户创建应用程序(客户端)和开发智能合约。开发人员还编写代码,使区块链能够与传统应用程序交互:

区块链开发人员的主要角色是创建应用程序(和集成)和智能合约以及它们与企业网络和参与者的分类账和其他企业系统的交互。由于 Hyperledger Fabric 基础设施的分离,基础设施构造(如对等方、共识、安全、通道、策略)和开发人员领导的活动(如智能合约开发、部署、企业集成、API 管理和前端应用程序开发)之间存在明确的分离。

从开发人员的角度来看,以下概述代表了开发人员与 Hyperledger Fabric 构造交互的一个例子:

  • 开发人员创建应用程序和智能合约

  • 应用程序可以通过 SDK 调用智能合约内部的调用。

  • 这些调用通过内置于智能合约中的业务逻辑和各种命令和协议来处理:

    • putdelete命令将经过所选的共识协议,并将被添加到区块链中。

    • get命令只能从世界状态中读取,但不会记录在区块链上。

  • 应用程序可以使用诸如get block height的 rest API 访问块信息。

请注意在此处使用了 delete——delete 可以从世界状态数据库中删除密钥,但不能删除区块链上的交易,因为我们已经确定这些交易是不可变的。

以下图表总结了所有关键角色:

理解区块链驱动的商业网络中的治理

治理可以定义为中心化或分散式机构,其唯一责任是在给定系统中建立一套规则或法律,以作出约束性决定。在区块链网络中,治理带来一系列挑战,在本节中,我们想讨论这些挑战以及区块链网络中的治理结构。在区块链的背景下,治理的话题呈现出一个有趣的悖论。

当一个区块链网络被创建时,治理结构通常是分布式的,由各方利益相关者提供输入。区块链网络以去中心化和自我治理为特征,具有内置的控制点和激励机制,以帮助维持适当的平衡。交易经过一系列去中心化的处理步骤,决策作为输出提供交易的最终确定性。这种治理结构基于激励经济学和共识。

区块链始于主要为无许可网络(例如,基于加密资产的网络,如比特币、莱特币等)的网络,依靠基于技术的系统治理通过激励和协调。当企业尝试应用区块链原则时,这种系统治理在商业世界中会带来一些挑战。企业世界受到严格监管,因此依靠具有制衡的许可区块链模型;考虑到各种数据法规、受托责任和正在进行交易的竞争实体之间的潜在利益冲突,这可能变得相当复杂。由于机密性和隐私问题,不能必然存在相同类型的激励或协调。

企业的重点通常是了解区块链技术及其对业务的潜在影响。治理现在已经成为企业区块链世界中一个有趣的新兴学科——也是一个重要的学科。从区块链商业模型的讨论中可以看出,存在各种可能的治理结构,从完全去中心化和准去中心化到完全集中化的区块链网络。治理结构实际上决定了区块链采用的许多其他方面,从设计到运营再到增长模型。商业模型和治理结构密切相连且相互依存;两者都直接影响区块链网络运作的各个方面。

治理结构和格局

那种依靠网络参与者之间的激励和协调的系统治理对于解决更多受监管行业及其用例是不够的。因此,我试图为更传统的企业定义一个治理结构和格局,这是一种利用现有最佳实践的模块化方法。

这个模型旨在促进进步和增长,但提供了网络参与者的必要分离。我将概述的简化治理结构建立在区块链的核心原则以及激励、惩罚、灵活性、委托和协调的原则之上。请记住,利用区块链的目标是发展信任网络,同时强制执行某些参与规则。一般来说,区块链项目的目标是激励技术和安全的升级,并惩罚不遵守规定的行为,希望确保继续参与和共享区块链网络带来的商业利益。我再次描述的业务治理模型不仅有助于在这样的网络中公平参与,还有助于公平的成本结构。本节提供了一个高层次的背景。我们在专门讨论治理的章节中讨论了更多细节。

信息技术治理

IT 治理的学科专注于 IT 基础设施、性能、成本结构和风险。在分散的区块链网络中,这会带来一些挑战,因为治理框架应建立问责制,以鼓励良好行为和网络 IT 基础设施的最佳运行。区块链网络的技术设计和基础设施选择应能够适应参与者的需求。由于区块链网络在至少某种程度上是分散的,IT 治理应包括分布式灵活性和分布式控制。

IT 治理应至少提供以下内容:

  • 分布式 IT 管理结构

  • 分布式维护、升级等模型

  • 利用行业标准——COBIT、ITIL、ISO、CMMI、FAIR 等

  • 资源优化——这包括技术采购、供应商关系、SLA 管理、技能和人才管理

  • 技术采用和评估以跟上技术演进

  • 网络部署策略,以鼓励和强制定期更新和升级

  • 网络支持服务——IT SLA 执行和会员服务

  • 风险优化——运营支持服务OSSs)和业务支持服务BSSs),IT 基础设施连续服务/规划,技术与法律和法规要求的对齐,等等

区块链网络治理

治理可以涉及以下内容:

  • 管理参与网络

  • 形成公平的成本结构,根据参与者的活动公平分配

  • 允许志同道合的参与实体参与交易和价值创造

  • 管理参与规则和社会契约,以促进公平

区块链网络治理包括以下内容:

  • 吸纳和退出成员

  • 建立公平的成本结构

  • 详细说明数据所有权的工作方式

  • 监管监督和合规报告

  • 管理具有中央管理和投票流程的许可结构,联邦结构和委派结构

  • 管理业务运营和 SLA

  • 网络支持服务(与 IT 治理相同)

  • 风险优化(与 IT 治理相同)

商业网络治理

管理由区块链支持的业务网络将需要一个特定于用例和行业的模型,考虑该行业的发展和特殊情况。这种治理结构将是多组织的,并且参与组织需要通过他们的集体贡献对网络功能有广泛的理解,以实现最佳结果。随着新的参与者被添加或移除,并且区块链网络发展,其动态也会改变。

共创的概念意味着将各方聚集在一起,产生互利和有价值的结果。一个例子可以是将一家公司与一群客户团结在一起,产生新的想法,并听取新的观点。

以下是商业网络治理可能包括的非尽量列表:

  • 制定商业模式,网络运作规则和法律章程

  • 网络中通用的共享的服务管理,比如了解你的客户流程,审计,报告等

  • 与网络相关的沟通

  • 质量保证和绩效测量

  • 监控和管理网络安全

  • 产品和业务网络发展计划

  • 法律和监管框架的执行

  • 确保符合行业特定要求的策略

  • 设立技术和网络的管理者

区块链网络的治理结构可能是一个有趣的挑战。正如我所展示的,关于区块链网络的完全去中心化,准去中心化和完全中心化仍然存在着相当大的辩论,这实际上取决于治理结构。我的意思是,区块链网络的治理结构有助于决定什么样的互动,发展,技术选择和运营最适合该网络。正如我之前所述,区块链是一个能够实现共创的平台,从中产生的新的协同效应将需要通过 SLAs 和健全的治理结构进行一些管理。治理将在第十章中进行详细讨论,治理,受监管行业的必要之恶

总结

所有这些都有助于吸引新的网络参与者,以及维持创始和现有参与者的信心,同时保持商业利益和价值。

业务模型和治理结构相互依赖,以正确管理区块链网络的运作。一个精心策划的治理模型将确保涉及的实体之间的和谐,它们可能在不同时间充当竞争者、共同创造者或合作者。

第三章:以业务场景为背景设定舞台

前两章着重于搭建一个区块链项目的舞台,并定义了一个业务框架和各种 Hyperledger 项目如何解决时间和信任问题。

通过了解构成 Hyperledger Fabric 的组件,我们现在将深入研究应用设计和实施方面的考虑。接下来的几章将带您完成创建自己的智能合约的步骤,然后将其集成到一个应用程序中。

为了使这些练习相关,我们将利用一个源自一些古老文明的业务用例:贸易和信用证。

本章的目标是介绍信用证的业务概念,带您走过我们选择的样例场景,并通过设置我们的开发环境来结束。

在本章中,我们将:

  • 探索信用证

  • 回顾我们简化的业务场景

  • 设置我们的开发环境

贸易和信用证

退回到历史的某个时刻,当商人穿越大陆购买一国的布料去另一个国家出售时。作为佛罗伦萨的羊毛商人,你可能会前往阿姆斯特丹购买这个新成立的城邦的优质羊毛,该港口汇集了整个北欧乃至更远地区的资源。然后你可以将羊毛运到佛罗伦萨,在那里可以卖给为富有客户制作精美服装的裁缝。我们谈论的是公元 1300 年——这是一个携带黄金或其他贵重金属作为货币购买和出售商品不安全的时代。必需的是一种跨越国界的货币形式,可以在阿姆斯特丹和佛罗伦萨以及任何地方使用!

马可·波罗曾去过中国,看到了那个繁荣经济中的商业活动是如何进行的。在成功的可汗帝国的核心是我们今天会认识到的先进金融技术。法定货币、纸币、本票和信用证都是通过中国传入欧洲的。马可·波罗将这些想法带回了欧洲——它们帮助形成并发展了罗马帝国灭亡后新兴的欧洲的一个商业银行业。

信任在促进贸易中的重要性

现在我们的佛罗伦萨商人可以联系他的银行家,告诉他他想在阿姆斯特丹购买羊毛,银行将以账上付款的方式给他出具一张信用证。这封信可以有各种规定,例如交易的最大金额、如何支付(一次性或分期付款)、可以用于哪些商品等。商人现在将前往阿姆斯特丹,在羊毛商选择羊毛后,他将提供信用证作为付款。阿姆斯特丹商人会高兴地将羊毛与信用证交换,因为当涉及到金钱时,佛罗伦萨银行家在整个欧洲都以信誉良好而闻名。阿姆斯特丹商人可以将信用证带给他的银行家,后者将会给他的账户记入贷方。当然,佛罗伦萨和阿姆斯特丹的银行家都会向各自的客户——商人——收取这项服务的费用!这对每个人都有好处。

定期,阿姆斯特丹银行家和佛罗伦萨银行家会见面来结算账户,但这对于羊毛贸易商和羊毛商人来说毫无意义。事实上,发生的是佛罗伦萨和阿姆斯特丹商人利用各自银行家之间的信任来建立彼此之间的信任关系——这是一个非常复杂的想法,当你想一想就会明白。这就是为什么信用证流程至今仍然是全球业务的基本方式。

今日信用证流程

然而,随着贸易的大规模全球化和金融行业的爆炸性增长,参与信用证流程的金融机构数量激增!如今,可能有超过 20 家中介金融机构参与其中。这需要协调许多人和系统,从而导致整个过程对商家和银行都产生了过多的时间、成本和风险。

区块链的承诺是提供一个逻辑上单一但物理上分布式的系统,为低摩擦的信用证流程提供平台。这样的系统特征将包括更大的透明度、及时性和自动化(导致成本降低),以及增量支付等新特性。

商业场景和用例

国际贸易包括那些说明了区块链旨在减轻的现实世界流程中的低效率和不信任的情况。因此,我们选择了一个进口-出口场景的元素,其中包含在接下来几章的实际练习中进行的简化版本的交易作为我们的规范用例。

概述

我们将描述的情景涉及一笔简单的交易:从一方向另一方出售货物。由于买方和卖方住在不同的国家,这笔交易变得复杂,因此没有共同的可信介质来确保出口商得到承诺的款项以及进口商得到货物。今天世界上的贸易安排依赖于以下内容:

  • 促成付款和货物实际转移的中介

  • 随着时间的推移已发展出来的流程,以使出口商和进口商对冲风险并降低风险

现实世界中的流程

促进支付和货物实际转移的中间人是出口商和进口商的各自银行。在这种情况下,贸易安排通过银行与其客户之间以及两家银行之间的信任关系来实现。这些银行通常具有国际联系和声誉需要维护。因此,进口商银行的承诺(或承诺)向出口商银行付款足以触发流程。出口商从出口国政府获得监管清关许可后,通过知名的国际承运人发运货物。

向承运人交付的交付证明足以使进口商银行向出口商银行清算付款,这种清算不取决于货物是否到达预定目的地(假设货物在运输过程中已经购买了丢失或损坏的保险)。进口商银行向出口商银行做出的支付承诺指定所需的一系列文件作为发运证明,以及立即或分期付款的具体支付方式。出口商必须在交付货物给承运人之前满足各种监管要求,以获得文件清关许可。

简化和修改过的流程

我们的用例将遵循前述流程的简化版本,带有某些变化以展示区块链在便利贸易方面的价值。进口商的银行向出口商的银行承诺支付两期付款。出口商从监管机构获得清关证书,将货物交由承运人,然后获得收据。收据的出具触发进口商银行向出口商银行支付第一期付款。当货物到达目的港口时,进行第二和最后一期的支付,流程结束。

贸易金融和物流中使用的术语

以下术语用于指代贸易场景中正在使用的某些工具和物品。我们在本章中构建的应用程序使用这些工具的非常简化形式:

  • 信用证:正如我们在本章开头所见,这指的是银行承诺在出具货物装运的文件证明后向出口商付款。简称 L/C,此文件由进口商的银行根据其客户(进口商)的要求出具。L/C 列明了构成装运证明的文件清单、要支付的金额以及受益人(在我们的案例中为出口商)的金额。下图显示了一份样本 L/C:

我们将在我们的使用案例中引入一些小的变化,以使读者能够理解此工具。首先,信用证将由出口商的银行开具,而不是直接由出口商开具。其次,信用证规定支付将分两期进行,第一期在出具两份文件后支付,第二期在货物到达目的地后支付。

  • 出口许可证:指出口国监管机构对指定货物的运输所给予的批准。在本书中,我们将其简称为 E/L。下图显示了一份样本 E/L:

  • 提单:这是承运人在接收货物后向出口商出具的文件。简称 B/L,它同时起到收据、合同(约束承运人将货物运送到指定目的地以换取报酬)、货物所有权证明的作用。该文件也列在信用证中,并作为装运证明,将自动触发付款结清。下图显示了一份样本 B/L:

共享流程工作流程

本章节中呈现的每一个测试用例场景都需要很长时间才能完成,涉及不同时间段不同集合实体之间的互动,并且有许多不同的移动部分,很难跟踪。我们希望使用我们的工作流程来简化这个过程。在区块链上实施的交易序列描述如下步骤(并在下图中说明)可以以不可撤销和不可否认的方式进行。在这一事件序列中,我们假设一个笔直的、线性的叙述,各方互相达成协议,没有发生任何意外事件;过程中只内置了一些用于捕捉错误的保障。

我们工作流程中的交易如下所示:

  1. 进口商请求出口商以货物换取货款

  2. 出口商接受交易协议

  3. 进口商向其银行要求开具一份有利于出口商的信用证

  4. 进口商的银行向出口商提供一份有利于出口商且可支付给后者银行的 L/C

  5. 出口商的银行代表出口商接受信用证

  6. 出口商向监管机构申请 E/L

  7. 监管机构向出口商提供 E/L

  8. 出口商准备了一批货物并交给承运人

  9. 承运人在验证 E/L 后接受货物,然后向出口商提供 B/L

  10. 出口商的银行向进口商的银行索取一半支付款

  11. 进口商的银行将一半金额转账给出口商的银行

  12. 承运人将货物运送到目的地

  13. 进口商的银行向出口商的银行支付剩余金额

这是一个解释交易流程的图表:

共享资产和数据

上一个工作流程中的参与者必须有一些共同的信息,让他们能够随时查看交易安排及其进展。

以下是参与者拥有的资产表,这些资产与其他人共享,驱动流程从一阶段到下一阶段。这包括文件和货币资产:

资产类型 资产属性
信用证 ID、发行日期、到期日期、发行方、受益人、金额和文件列表
提单 ID、发货人(出口商)、收货人(进口商)、通知方(进口商的银行)、收发货地点、货物描述和运费金额
出口许可证 ID、发行日期、到期日期、受益人、许可证持有者和货物描述
付款 标准货币单位金额

以下是界定参与者在每个阶段可选择的选择的数据元素:

数据类型 数据属性
贸易协议 进口商请求,出口商接受
信用证 进口商请求,进口商银行发行,出口商银行接受
出口许可证 请求者是出口商,由监管机构发行
货物运输 出口商准备,承运人接受,当前位置或地点

参与者的角色和能力

在我们的场景中有六类参与者:出口商、进口商、出口商银行、进口商银行、承运人和监管机构。这个集合中的术语指的是实体在贸易交易中可以承担的角色;例如,一家公司在一次出口货物交易中可能是进口商。每个角色的能力和限制也在以下列表中详细说明:

  • 只有进口商可以申请 L/C

  • 只有进口商的银行可以提供 L/C

  • 只有出口商的银行可以接受 L/C

  • 只有出口商才能请求 E/L

  • 只有监管机构才能提供 E/L

  • 只有出口商可以准备货物

  • 只有承运人可以提供 B/L

  • 只有承运人可以更新货物位置

  • 只有进口商的银行可以汇款,只有出口商的银行可以接收款项

区块链应用的优势超过当前现实世界的流程

在缺乏保障的情况下转移货物或进行支付所固有的风险(如缺乏可信的调解者)催生了银行的参与,并导致信用证和提单的产生。这些过程的一个结果不仅仅是额外的成本(银行要收取佣金来发行信用证),或者额外的开支。申请和等待授予出口许可证也会增加周转时间。在理想的贸易场景中,只有准备和运输货物的过程需要时间。最近,采用 SWIFT 消息传递比手动通信更高效,但并没有从根本上改变游戏规则。另一方面,区块链几乎即时的交易承诺和保证拓展了以前不存在的可能性。

作为示例,我们在我们的应用场景中引入的一个变化是分期付款,这在传统框架中无法实现,因为没有一种可靠的方式来了解和共享关于货物进展的信息。在这种情况下,这样的变化被认为是太过风险,这就是为什么支付纯粹与文件证据相关联。通过让交易协议中的所有参与方在一个共同的区块链上实施一个公共智能合同,我们可以提供一个共享的真实性来源,从而最小化风险,同时增加问责制。

在接下来的章节中,我们将详细演示我们的应用是如何在 Hyperledger Fabric 和 Composer 平台上实现的。读者将能够欣赏实现的简单性和优雅性,然后可以将其用作指南,来重塑其他应用的古老流程,利用这一激动人心的新技术。然而,在跳入代码之前,我们将研究 Hyperledger 网络的设计,并设置我们的开发环境。

设置开发环境

正如你现在已经知道的那样,Hyperledger Fabric 区块链的一个实例被称为通道,它是以加密方式相互关联的交易日志。要设计和运行区块链应用程序,第一步是确定需要多少个通道。对于我们的贸易应用程序,我们将使用一个通道,它将维护不同参与方之间进行的交易历史记录。

一个 Fabric 节点可能属于多个通道,从应用程序的角度来看,这些通道彼此毫无所知,但它们帮助单个节点代表其所有者(或客户)在不同应用程序中运行交易。一个通道可以运行多个智能合约,每个智能合约可以是独立的应用程序,或者链接在一起形成多合约应用程序。在本章和本书中,我们将为读者介绍一个简单的单通道、单合约应用程序的设计。读者可以根据本书提供的信息以及 Fabric 文档设计更复杂的应用程序。

在我们深入了解如何设置系统以安装应用程序并在智能合约上运行交易的机制之前,我们将描述如何创建和启动一个网络,该网络将安装应用程序。本章将使用一个样本网络结构来说明贸易操作(在第九章,区块链网络中的生活,您将看到如何根据需求的变化和发展修改此样本网络)。

设计网络

为了确定一个 Hyperledger Fabric 网络结构以供应用程序使用,第一步是列出参与的组织。在逻辑上,一个组织是一个安全域和身份与凭证的单位。它管理一个或多个网络节点,并依赖于一个成员服务提供商MSP)为节点和智能合约访问权限的客户颁发身份和证书。订购服务是 Fabric 网络的基石,通常被分配给一个独立的组织。下图说明了一个典型的节点网络结构,其中包括客户端、MSP 和逻辑组织分组。

交易(或调用)批准的标准是一个背书策略(我们将在本章后面重新讨论)。它是根据参与应用程序网络的组织来制定的,而不是节点本身。

图 3.1:区块链网络,节点分布在各个组织之间,客户端从组织获取凭证,以提交查询和调用链代码

必须提前决定一组节点、它们所属的组织以及为每个组织提供服务的成员服务提供商,以便在这些机器上安装和运行适当的服务。

我们的示例贸易网络将由四个组织组成,分别代表出口商、进口商、承运商和监管机构。后两者分别代表承运商和监管实体。然而,出口商组织既代表出口实体又代表其银行。同样地,进口商组织代表进口实体和其银行。将信任的实体与其交易方分组到单一组织中,从安全和成本的角度来看都是有意义的。运行一个 Fabric 节点是一个沉重且昂贵的业务,因此让拥有更多资源和大量客户的银行代表自身和其客户运行这样的节点是足够的。一个贸易实体在其组织中以客户端的角色获得提交交易或读取账本状态的权利。因此,我们的区块链网络需要四个同行,各自属于不同的组织。除了同行,我们的网络包括每个组织的一个 MSP,以及以单独模式运行的排序服务。

在生产应用中,排序服务应该作为 Zookeeper 上的 Kafka 集群设置,但为了展示如何构建区块链应用程序,排序服务可以被视为一个黑盒子。

排序服务属于自己的独立组织,带有一个 MSP。我们贸易网络的组织、其 MSP、同行和客户端在下面的图表中展示:

图 3.2:一个贸易网络,包括同行、排序者和各自组织中的客户端。

读者可能会想,如果一个贸易方和其银行属于同一个组织,那么应用程序如何区分两者(出口商和出口商的银行,以及进口商和进口商的银行),以控制对智能合约和账本的访问。这可以通过以下两种方式实现:

  • 嵌入中间件和应用层(我们将在本章后面描述)中的访问控制逻辑,用户可以通过其 ID(或登录名)区分,并维护将 ID 映射到允许的链码功能的访问控制列表。

  • 让一个组织的 MSP 作为 CA 服务器,嵌入其为组织成员颁发的证书中的区别属性。访问控制逻辑可以在中间件或链码中实现,以解析属性,并根据应用程序政策允许或拒绝操作。

这些机制没有在我们的应用中实现,其中银行家和客户对智能合约和中间件层来说是无法区分的。但读者可以将其视为一种练习,对于熟练于开发安全客户端-服务器应用程序的人来说,这应该是直截了当的。

安装先决条件。

在手持网络设计的情况下,让我们安装先决工具:

  1. 确保您拥有最新版本的:

  2. 我们将使用 GitHub 来共享教程的源代码。要访问 GitHub,需要安装 Git 客户端,并配置身份验证到 GitHub。有关更多信息,请访问 GitHub 的官方网站help.github.com/articles/set-up-git/

  3. 安装业务网络示例所需的软件:hyperledger.github.io/composer/latest/installing/installing-prereqs

    上述说明适用于 Mac 和 Linux。请注意,在使用 Windows 时,我们建议使用像 Vagrant 这样的解决方案在虚拟机中运行开发环境。

  4. Fabric 是用 Go 语言实现的。请注意:

    • Go 在语法上与 C++ 类似。

    • 我们还将使用 Go 来编写链代码。

    • Go 可以从golang.org/安装。

请注意,本书中的 Hyperledger Fabric 设置和教程应用程序的测试是使用 Go 1.9 完成的,因此建议读者安装并使用 1.9 或更高版本。

  1. 接下来,我们需要设置环境变量。

GOPATH 指向go源代码的工作空间,例如:

         $ export GOPATH=$HOME/go 

PATH 需要包括用于存储库和可执行文件的 Go bin 目录,如下面的代码片段所示:

         $ export PATH=$PATH:$GOPATH/bin 
  1. 验证系统上是否安装了 make。在 Debian/Ubuntu 系统上,您可以使用 sudo apt-get install make 安装它。

分叉并克隆贸易融资物流存储库

现在,我们需要通过在 GitHub 上分叉存储库来获取原始源代码的副本。然后,我们可以使用以下步骤将源代码克隆到本地机器目录中:

  1. 在 GitHub 中导航到以下存储库github.com/HyperledgerHandsOn/trade-finance-logistics

  2. 分叉存储库:使用页面右上角的分叉按钮创建源代码的副本到您的帐户

  3. 获取克隆 URL:导航到贸易融资物流存储库的您的分叉。单击“克隆或下载”按钮,并复制 URL。

  4. 克隆存储库:在 Go 工作空间中,按以下方式克隆存储库:

$ cd $GOPATH/src 
$ git clone https://github.com/YOUR-USERNAME/trade-finance-logistics

现在我们有了所有贸易融资物流教程材料的本地副本。

创建和运行网络配置

配置和启动我们网络的代码可以在我们存储库的 network 文件夹中找到(这是 fabric-samples/first-network 的一个改编)。在本次练习中,我们将在单个物理或虚拟机上运行整个网络,各种网络元素在适当配置的 Docker 容器中运行。假设读者对使用 Docker 进行容器化和使用 Docker-compose 进行配置有基本的了解。一旦满足前一节中列出的先决条件,只需运行该节中的命令,无需读者具备额外的知识或配置。

准备网络

要构建 Fabric 和 Fabric-CA,如果缺少某些依赖项,您可能需要安装某些依赖项。这些包括 gcclibtoolltdl 库。(在 Ubuntu Xenial 系统上,可以通过运行 sudo apt-get install libltdl-dev 安装所有必要的先决条件。读者需要在其他系统上寻找相应的等效物)。在生成网络加密材料之前,我们需要执行以下步骤。

教程应用是在 Hyperledger Fabric 版本 1.1 上开发的,因此您需要获取和构建该版本的组件。

  1. 克隆 Fabric (github.com/hyperledger/fabric/tree/release-1.1) 源代码存储库。如果您使用 git clone 命令,请添加参数 -b release-1.1。确保克隆的 fabric 文件夹存在于 $GOPATH/src/github.com/hyperledger/ 中,或者在该路径中创建符号链接。当您尝试构建 Fabric 时,它将在此路径中查找库。

  2. 运行 make docker 来为对等方和排序者构建 Docker 镜像。

  3. 运行 make configtxgen cryptogen 来生成运行本节描述的网络创建命令所需的必要工具。

  4. 克隆 Fabric-CA (github.com/hyperledger/fabric-ca/tree/release-1.1) 源代码存储库。(如果您使用 git clone 命令,请添加参数 -b release-1.1。确保克隆的 fabric-ca 文件夹存在于 $GOPATH/src/github.com/hyperledger/ 中,或者在该路径中创建符号链接。当您尝试构建 Fabric-CA 时,它将在此路径中查找库。

  5. 运行 make docker 来构建 MSPs 的 Docker 镜像。

生成网络加密材料

配置网络的第一步涉及为每个对等方和订购方组织的 MSP 以及基于 TLS 的通信创建证书和签名密钥。我们还需要为每个对等方和订购方节点创建证书和密钥,以便能够彼此通信以及与各自的 MSP 通信。这个配置必须在我们的代码库中的network文件夹中的crypto-config.yaml文件中指定。该文件包含组织结构(稍后在通道工件配置部分中更多细节),每个组织中对等方的数量以及必须为其中的每个用户创建证书和密钥的默认数量(请注意,默认情况下会创建一个admin用户)。例如,请参阅文件中 Importer 组织的定义如下:

PeerOrgs:
- Name: ImporterOrg
  Domain: importerorg.trade.com
  EnableNodeOUs: true
  Template:
    Count: 1
  Users:
    Count: 2

此配置指示标记为ImporterOrg的组织将包含一个对等方。还将创建两个非管理员用户。还定义了对等方要使用的组织域名。

要为所有组织生成加密材料,请执行以下cryptogen命令:

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

输出保存在crypto-config文件夹中。

生成通道工件

为了按照组织的结构创建网络,并引导一个通道,我们需要生成以下工件:

  • 初始块,包含用于初始化 Fabric 区块链的组织特定证书。

  • 通道配置信息。

  • 每个组织的锚定对等配置。锚定对等在组织内部充当支点,使用 Fabric 八卦协议进行跨组织账本同步。

crypto-config.yaml文件类似,通道属性在一个名为configtx.yaml的文件中指定,在我们的源代码中可以在network文件夹中找到。我们贸易网络的高级组织如下所示:

Profiles:
  FourOrgsTradeOrdererGenesis:
    Capabilities:
      <<: *ChannelCapabilities
    Orderer:
      <<: *OrdererDefaults
      Organizations:
        - *TradeOrdererOrg
      Capabilities:
        <<: *OrdererCapabilities
    Consortiums:
      TradeConsortium:
        Organizations:
          - *ExporterOrg
          - *ImporterOrg
          - *CarrierOrg
          - *RegulatorOrg
  FourOrgsTradeChannel:
    Consortium: TradeConsortium
    Application:
      <<: *ApplicationDefaults
      Organizations:
        - *ExporterOrg
        - *ImporterOrg
        - *CarrierOrg
        - *RegulatorOrg
      Capabilities:
        <<: *ApplicationCapabilities

正如我们所看到的,我们要创建的通道被命名为FourOrgsTradeChannel,在配置文件中定义。参与此通道的四个组织被标记为ExporterOrgImporterOrgCarrierOrgRegulatorOrg,每个组织都引用了Organizations部分中定义的子部分。订购方属于自己的组织称为TradeOrdererOrg。每个组织部分包含有关其 MSP 的信息(ID 以及加密材料的位置,如密钥和证书),以及其锚定对等方的主机名和端口信息。例如,ExporterOrg部分包含以下内容:

- &ExporterOrg
  Name: ExporterOrgMSP
  ID: ExporterOrgMSP
  MSPDir: crypto-config/peerOrganizations/exporterorg.trade.com/msp
  AnchorPeers:
    - Host: peer0.exporterorg.trade.com
    Port: 7051

正如你所看到的,这个规范中的MSPDir变量(表示一个文件夹)引用了我们之前使用cryptogen工具生成的加密材料。

要生成通道文件,我们使用configtxgen工具。要生成创世区块(将在网络引导期间发送到 orderer),请从network文件夹运行以下命令:

configtxgen -profile FourOrgsTradeOrdererGenesis -outputBlock ./channel-artifacts/genesis.block

FourOrgsTradeOrdererGenesis关键字对应于Profiles部分中的配置文件名称。创世区块将保存在channel-artifacts文件夹中的genesis.block文件中。要生成通道配置,请运行以下代码:

configtxgen -profile FourOrgsTradeChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID tradechannel

我们将创建的通道名为tradechannel,其配置存储在channel-artifacts/channel.tx中。要生成出口者组织的锚定 peer 配置,请运行:

configtxgen -profile FourOrgsTradeChannel -outputAnchorPeersUpdate ./channel-artifacts/ExporterOrgMSPanchors.tx -channelID tradechannel -asOrg ExporterOrgMSP

对于其他三个组织,应重复相同的过程,并在先前的命令中更改组织名称。

必须将环境变量FABRIC_CFG_PATH设置为指向包含configtx.yaml文件的文件夹,以便configtxgen工具正常工作。我们稍后将使用的trade.sh脚本文件包含以下行以确保从运行命令的文件夹加载YAML文件:

export FABRIC_CFG_PATH=${PWD}

在一个操作中生成配置

为了方便起见,trade.sh脚本已配置为使用先前描述的命令和配置文件生成通道文件以及加密材料。只需从network文件夹中运行以下命令:

./trade.sh generate -c tradechannel

虽然您可以在这里指定任何通道名称,但请注意,本章后面用于开发中间件的配置将依赖于该名称。

GOPATH变量在运行 peer 的容器中设置为/opt/gopath

组合一个样例交易网络

最后一个命令还具有生成网络配置文件docker-compose-e2e.yaml的效果,该文件用于使用 docker-compose 工具在一组 Docker 容器中启动网络。文件本身依赖于静态配置文件base/peer-base.yamlbase/docker-compose-base.yaml。这些文件共同指定服务及其属性,并使我们能够一次性在 Docker 容器中运行它们,而不是在一个或多个机器上手动运行这些服务的实例。我们需要运行的服务如下:

  • 四个 Fabric peer 实例,每个组织一个

  • 一个 Fabric orderer 实例

  • 五个 Fabric CA 实例,对应于每个组织的 MSP

每个都可以从 Docker Hub 上的 Hyperledger 项目(hub.docker.com/u/hyperledger/)获取 Docker 镜像,其中镜像分别为hyperledger/fabric-peerhyperledger/fabric-ordererhyperledger/fabric-ca for peersorderersMSPs

一个 peer 的基本配置可以如下(参见base/peer-base.yaml):

peer-base:
image: hyperledger/fabric-peer:$IMAGE_TAG
environment:
  - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
  - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=${COMPOSE_PROJECT_NAME}_trade
  - CORE_LOGGING_LEVEL=INFO
  - CORE_PEER_TLS_ENABLED=true
  - CORE_PEER_GOSSIP_USELEADERELECTION=true
  - CORE_PEER_GOSSIP_ORGLEADER=false
  - CORE_PEER_PROFILE_ENABLED=true
  - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
  - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
  - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
command: peer node start

Fabric 配置参数可以在此设置,但如果您使用fabric-peer的预构建 Docker 映像,则默认设置足以运行对等方服务。在配置的最后一行指定运行对等方服务的命令为peer node start;如果您希望通过下载 Fabric 源代码并在本地机器上构建来运行对等方,则这是您将要运行的命令(有关示例,请参见Chapter 4使用 Golang 设计数据和事务模型)。还请确保使用CORE_LOGGING_LEVEL变量适当配置日志级别。在我们的配置中,该变量设置为INFO,这意味着仅记录信息、警告和错误消息。如果您希望调试对等方并需要更广泛的日志记录,请将此变量设置为DEBUG

IMAGE_TAG变量在network文件夹中的.env文件中设置为 latest,尽管如果您希望拉取旧的映像,可以设置特定的标签。

此外,我们需要为每个对等方配置主机名和端口,并将使用cryptogen生成的加密材料同步到容器文件系统。导出器组织中的对等方在base/docker-compose-base.yaml中配置如下:

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

正如extends参数所示,这扩展了基本配置。注意,ID(CORE_PEER_ID)与configtx.yaml中为此对等方指定的 ID 相匹配。该标识是运行在导出器组织中的对等方的主机名,并且稍后在本章的中间件代码中将使用它。卷部分指示将在crypto-config文件夹中生成的加密材料复制到容器的规则。对等方服务本身侦听端口7051,客户端用于订阅事件的端口设置为7053

在文件中,您会看到容器内端口在对等方之间是相同的,但映射到主机机器上的端口是不同的。最后,请注意,此处指定的 MSP ID 也与configtx.yaml中指定的相匹配。

订购者服务的配置类似,如base/docker-compose-base.yaml中的以下片段所示:

orderer.trade.com:
  container_name: orderer.trade.com
  image: hyperledger/fabric-orderer:$IMAGE_TAG
  environment:
    - ORDERER_GENERAL_LOGLEVEL=INFO
  ……
  command: orderer
  ……

启动订购者的命令很简单,就像代码所示的那样是orderer。日志级别可以使用ORDERER_GENERAL_LOGLEVEL变量进行配置,在我们的配置中设置为INFO

我们将要运行的实际网络配置是基于一个名为docker-compose-e2e.yaml的文件。这个文件并不在存储库中,而是通过我们先前运行的./trade.sh generate -c tradechannel命令创建的。这个文件依赖于base/docker-compose-base.yaml(间接地也依赖于base/peer-base.yaml),你可以通过检查文件内容来看到。它实际上是从一个名为docker-compose-e2e-template.yamlYAML模板文件创建的,你可以在network文件夹中找到。模板文件包含变量作为cryptogen生成的关键文件的代理。当docker-compose-e2e.yaml被生成时,这些变量名称会被实际文件名替换,这些文件存在于crypto-config文件夹中。

例如,考虑docker-compose-e2e-template.yaml中的exporter-ca部分:

exporter-ca:
  image: hyperledger/fabric-ca:$IMAGE_TAG
  environment:
    ……
    - FABRIC_CA_SERVER_TLS_KEYFILE=/etc/hyperledger/fabric-ca-server-config/EXPORTER_CA_PRIVATE_KEY
  ……
  command: sh -c '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/EXPORTER_CA_PRIVATE_KEY -b admin:adminpw -d'

现在,看看在生成的文件docker-compose-e2e.yaml中的相同部分:

exporter-ca:
  image: hyperledger/fabric-ca:$IMAGE_TAG
  environment:
    ……
    - FABRIC_CA_SERVER_TLS_KEYFILE=/etc/hyperledger/fabric-ca-server-config/ cc58284b6af2c33812cfaef9e40b8c911dbbefb83ca2e7564e8fbf5e7039c22e_sk
  ……
  command: sh -c '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'

如你所见,环境变量和命令中的变量EXPORTER_CA_PRIVATE_KEY已被替换为cc58284b6af2c33812cfaef9e40b8c911dbbefb83ca2e7564e8fbf5e7039c22e_sk。如果你现在检查crypto-config文件夹的内容,你会注意到在crypto-config/peerOrganizations/exporterorg.trade.com/ca/文件夹中存在一个名为cc58284b6af2c33812cfaef9e40b8c911dbbefb83ca2e7564e8fbf5e7039c22e_sk的文件。这个文件包含了出口商组织 MSP 的私(秘密)签名密钥。

前述的代码片段包含了一个样本运行的结果。每当你运行加密材料生成工具时,关键文件名将会有所变化。

让我们现在更详细地看一下 MSP 的配置,以出口商组织 MSP 为例,如docker-compose-e2e.yaml中所指定的那样:

exporter-ca:
  image: hyperledger/fabric-ca:$IMAGE_TAG
  environment:
    - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
    - FABRIC_CA_SERVER_CA_NAME=ca-exporterorg
    - FABRIC_CA_SERVER_TLS_ENABLED=true
    - FABRIC_CA_SERVER_TLS_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.exporterorg.trade.com-cert.pem
    - FABRIC_CA_SERVER_TLS_KEYFILE=/etc/hyperledger/fabric-ca-server-config/cc58284b6af2c33812cfaef9e40b8c911dbbefb83ca2e7564e8fbf5e7039c22e_sk
  ports:
    - "7054:7054"
  command: sh -c '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'
  volumes:
    - ./crypto-config/peerOrganizations/exporterorg.trade.com/ca/:/etc/hyperledger/fabric-ca-server-config
  container_name: ca_peerExporterOrg
  networks:
    - trade

将在 MSP 中运行的服务是fabric-ca-server,监听端口7054,使用cryptogen创建的证书和密钥进行引导,并使用fabric-ca镜像中配置的默认登录名和密码(分别为adminadminpw)。启动一个 Fabric CA 服务器实例的命令是fabric-ca-server start ...,如你在前述代码中所看到的。

同样,对等方和 CA 都配置为基于 TLS 的通信,正如前述配置所示。读者必须注意,如果一个地方禁用了 TLS,另一个地方也必须禁用 TLS。

同样,通过检查docker-compose-e2e.yaml,我们可以看到,我们没有为订购方的组织创建 Fabric CA 服务器(和容器)。对于在本书中要进行的练习,静态创建的管理用户和订购者的凭据足够了;我们不会动态注册新的订购者组织用户,所以不需要 Fabric CA 服务器。

网络组件的配置文件

我们已经演示了如何在 docker-compose YAML 文件中配置对等方、订购者和 CA。但是这些配置意味着覆盖了组件的默认设置。虽然这些配置的详细说明超出了本书的范围,但我们将列出各自的文件并提及用户如何对其进行更改。

对于一个对等方,一个名为 core.yaml 的文件(github.com/hyperledger/fabric/blob/release-1.1/sampleconfig/core.yaml)包含了所有重要的运行时设置,包括但不限于地址、端口号、安全和隐私以及八卦协议。您可以创建自己的文件并使用自定义 Dockerfile 将其同步到容器中,而不是使用默认的 hyperledger/fabric-peer 镜像。如果您登录到一个正在运行的对等方容器(让我们从我们刚刚启动的网络中取出 Exporter 组织的对等方容器):

docker exec -it f86e50e6fc76 bash

然后您将在文件夹 /etc/hyperledger/fabric/ 中找到 core.yaml 文件。

类似地,订购者的默认配置位于一个名为 orderer.yaml 的文件中(github.com/hyperledger/fabric/blob/release-1.1/sampleconfig/orderer.yaml),该文件也与运行 hyperledger/fabric-orderer 镜像的容器中的 /etc/hyperledger/fabric/ 同步。请注意,core.yamlorderer.yaml 文件都同步到对等方和订购者容器,因此,如果您希望创建自定义文件,则需要将这些 YAML 文件同步到这两个容器。

Fabric CA 服务器还有一个名为 fabric-ca-server-config.yaml 的配置文件(hyperledger-fabric-ca.readthedocs.io/en/latest/serverconfig.htm),它与运行 hyperledger/fabric-ca 镜像的容器中的 /etc/hyperledger/fabric-ca-server/ 同步。您可以像为对等方或订购者创建和同步自定义配置一样创建和同步自定义配置。

启动示例交易网络

因此,现在我们已经有了网络的所有配置,以及运行所需的通道工件和加密材料,我们所需做的就是使用 docker-compose 命令启动网络,如下所示:

docker-compose -f docker-compose-e2e.yaml up

您可以将此作为后台进程运行,并将标准输出重定向到 log 文件,如果您愿意的话。否则,您将看到各种容器启动和每个容器的日志显示在控制台上。

请注意,在某些操作系统配置中,设置 Fabric 可能会有些棘手。如果遇到问题,请查阅文档。有关如何安装 Fabric 网络以及示例的详细说明,请访问 hyperledger-fabric.readthedocs.io/en/release-1.1/samples.html

网络也可以使用我们的 trade.sh 脚本在后台启动;只需运行:

./trade.sh up

从另一个终端窗口,如果您运行 docker ps -a,您将看到以下内容:

CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES
4e636f0054fc    hyperledger/fabric-peer:latest    "peer node start"    3 minutes ago    Up 3 minutes    0.0.0.0:9051->7051/tcp, 0.0.0.0:9053->7053/tcp    peer0.carrierorg.trade.com
28c18b76dbe8    hyperledger/fabric-peer:latest    "peer node start"    3 minutes ago    Up 3 minutes    0.0.0.0:8051->7051/tcp, 0.0.0.0:8053->7053/tcp    peer0.importerorg.trade.com
9308ad203362    hyperledger/fabric-ca:latest    "sh -c 'fabric-ca-se..."    3 minutes ago    Up 3 minutes    0.0.0.0:7054->7054/tcp    ca_peerExporterOrg
754018a3875e    hyperledger/fabric-ca:latest    "sh -c 'fabric-ca-se..."    3 minutes ago    Up 3 minutes    0.0.0.0:8054->7054/tcp    ca_peerImporterOrg
09a45eca60d5    hyperledger/fabric-orderer:latest    "orderer"    3 minutes ago    Up 3 minutes    0.0.0.0:7050->7050/tcp    orderer.trade.com
f86e50e6fc76    hyperledger/fabric-peer:latest    "peer node start"    3 minutes ago    Up 3 minutes    0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp    peer0.exporterorg.trade.com
986c478a522a    hyperledger/fabric-ca:latest    "sh -c 'fabric-ca-se..."    3 minutes ago    Up 3 minutes    0.0.0.0:9054->7054/tcp    ca_peerCarrierOrg
66f90036956a    hyperledger/fabric-peer:latest    "peer node start"    3 minutes ago    Up 3 minutes    0.0.0.0:10051->7051/tcp, 0.0.0.0:10053->7053/tcp    peer0.regulatororg.trade.com
a6478cd2ba6f    hyperledger/fabric-ca:latest    "sh -c 'fabric-ca-se..."    3 minutes ago    Up 3 minutes 0.0.0.0:10054->7054/tcp    ca_peerRegulatorOrg

我们有四个对等节点、四个 MSP 和一个运行在不同容器中的订购者。我们的交易网络已经启动并准备运行我们的应用程序!

要查看给定容器的运行日志,请注意容器 ID(上述列表中的第一列),然后简单运行:

docker logs <container-ID>

若要关闭网络,您可以使用 docker-compose 命令:

docker-compose -f docker-compose-e2e.yaml down

或者我们的 trade.sh 脚本:

./trade.sh down

总结

在本章中,我们介绍了我们后续章节将利用的业务用例,以便为我们将要编写的代码创建一个上下文。我们还部署了我们的第一个 Hyperledger Fabric 网络,现在已经从理论转向实践。干得好!

下一章将从两个角度带您了解区块链应用程序的开发:(1)使用链码和 Fabric SDK 的基础 API (2)使用 Hyperledger Composer 的业务网络实现。

通过这两个角度,我们希望能让您了解解决方案的灵活性以及在正确环境中利用每个工具的能力。要为下一章做好准备,您现在应该使用 ./trade.sh down 停止您的网络。

第四章:用 Golang 设计数据和交易模型

在超级账本 Fabric 中,链代码是由开发人员编写的智能合约的一种形式。链代码实现了区块链网络的利益相关者所达成一致的业务逻辑。该功能对客户端应用程序开放,以便它们调用,前提是它们具有正确的权限。

链代码作为一个独立的进程在自己的容器中运行,与 Fabric 网络的其他组件隔离开来。背书节点负责管理链代码和交易调用的生命周期。作为对客户端调用的回应,链代码查询和更新分类账,并生成交易提案。

在本章中,我们将学习如何用 Go 语言开发链代码,并实现场景的智能合约业务逻辑。最后,我们将探讨开发完全功能的链代码所必需的关键概念和库。

在接下来的章节中,我们将探索与概念相关的代码片段,你可以在以下地址找到链代码的完整实现:

github.com/HyperledgerHandsOn/trade-finance-logistics/tree/master/chaincode/src/github.com/trade_workflow_v1

请注意,这也可以在我们在上一章中创建的本地 git 克隆中找到。我们有两个版本的链代码,一个在trade_workflow文件夹中,另一个在trade_workflow_v1文件夹中。我们需要两个版本来演示稍后升级,在第九章区块链网络的生活中。在本章中,我们使用v1版本来演示如何用 Go 编写链代码。

在本章中,我们将讨论以下主题:

  • 创建链代码

  • 访问控制

  • 实现链代码函数

  • 测试链代码

  • 链代码设计主题

  • 记录输出

启动链代码开发

在我们可以开始编写链代码之前,我们首先需要启动我们的开发环境。

设置开发环境的步骤已在第三章使用商业情景设定舞台中解释过。然而,我们现在继续启动开发模式下的 Fabric 网络。这种模式允许我们控制如何构建和运行链代码。我们将使用此网络在开发环境中运行我们的链代码。

下面是我们如何以开发模式启动 Fabric 网络的步骤:

$ cd $GOPATH/src/trade-finance-logistics/network
$ ./trade.sh up -d true  

如果在启动网络时遇到任何错误,可能是由一些残留的 Docker 容器引起的。

你可以通过停止网络使用./trade.sh down -d true,然后运行以下命令解决这个问题:./trade.sh clean -d true

-d true 选项告诉我们的脚本在开发网络上采取行动。

我们的开发网络现在在五个 Docker 容器中运行。网络由单个订购者、在devmode中运行的单个对等体、一个链码容器、一个 CA 容器和一个 CLI 容器组成。CLI 容器在启动时创建了一个名为tradechannel的区块链通道。我们将使用 CLI 与链码交互。

随时查看日志目录中的日志消息。它列出了网络启动期间执行的组件和函数。我们将保持终端开放,因为一旦安装并调用了链码,我们会在这里收到进一步的日志消息。

编译和运行链码

克隆源代码已经使用 Go vendoring 包含了所有的依赖关系。考虑到这一点,我们现在可以开始构建代码,并使用以下步骤运行链码:

  1. 编译链码:在一个新的终端中,连接到链码容器并使用以下命令构建链码:
$ docker exec -it chaincode bash 
$ cd trade_workflow_v1 
$ go build 
  1. 使用以下命令运行链码:
$ CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=tw:0 ./trade_workflow_v1  

我们现在有一个连接到对等体的运行中的链码。这里的日志消息表明链码已经启动并正在运行。您还可以在网络终端中检查日志消息,其中列出了对等体上与链码的连接。

安装和实例化链码

在我们启动通道之前,现在需要在通道上安装链码,这将调用Init方法:

  1. 安装链码:在一个新的终端中,连接到 CLI 容器并按如下方式安装名称为tw的链码:
$ docker exec -it cli bash 
$ peer chaincode install -p chaincodedev/chaincode/trade_workflow_v1 -n tw -v 0
  1. 现在,实例化以下链码
$ peer chaincode instantiate -n tw -v 0 -c '{"Args":["init","LumberInc","LumberBank","100000","WoodenToys","ToyBank","200000","UniversalFreight","ForestryDepartment"]}' -C tradechannel 

CLI 连接的终端现在包含了与链码交互的日志消息列表。链码终端显示了来自链码方法调用的消息,而网络终端显示了对等体和订购者之间通信的消息。

调用链码

现在我们有一个正在运行的链码,我们可以开始调用一些函数。我们的链码有几种创建和检索资产的方法。目前,我们只会调用其中两个;第一个是创建一个新的交易协议,第二个是从分类账中检索它。要做到这一点,请完成以下步骤:

  1. 使用以下命令在分类账上放置具有唯一 IDtrade-12的新交易协议:
$ peer chaincode invoke -n tw -c '{"Args":["requestTrade", "trade-12", "50000", "Wood for Toys"]}' -C tradechannel
  1. 使用以下命令从分类账中检索 ID 为trade-12的交易协议:
$ peer chaincode invoke -n tw -c '{"Args":["getTradeStatus", "trade-12"]}' -C tradechannel

现在,我们在devmode中有一个正在运行的网络,我们已成功测试了我们的链码。在接下来的部分,我们将学习如何从头开始创建和测试链码。

开发模式

在生产环境中,链码的生命周期由对等体管理。当我们需要在开发环境中重复修改和测试链码时,我们可以使用devmode,它允许开发人员控制链码的生命周期。此外,devmodestdoutstderr标准文件重定向到终端;这些在生产环境中是禁用的。

要使用 devmode,对等方必须连接到其他网络组件,就像在生产环境中一样,并且以参数 peer-chaincodedev=true 启动。然后,链代码将单独启动并配置为连接到对等方。在开发过程中,可以从终端重复编译、启动、调用和停止链代码。

在接下来的部分中,我们将使用启用了 devmode 的网络。

创建链代码

现在我们已经准备好开始实现我们的链代码了,我们将使用 Go 语言编程。有几个提供对 Go 的支持的 IDE 可用。其中一些较好的 IDE 包括 Atom、Visual Studio Code 等等。无论您选择的环境如何,都可以与我们的示例一起使用。

链代码接口

每个链代码都必须实现 Chaincode interface,其方法是在接收到交易提案后调用的。SHIM 包中定义的 Chaincode interface 如下所示:

type Chaincode interface { 
    Init(stub ChaincodeStubInterface) pb.Response 
    Invoke(stub ChaincodeStubInterface) pb.Response 
} 

正如你所看到的,Chaincode 类型定义了两个函数:InitInvoke

这两个函数都有一个参数,类型为 ChaincodeStubInterface,名为 stub

stub 参数是我们在实现链代码功能时将使用的主要对象,因为它提供了访问和修改账本、获取调用参数等功能。

另外,SHIM 包提供了其他类型和函数来构建链代码;你可以在以下链接检查整个包:godoc.org/github.com/hyperledger/fabric/core/chaincode/shim.

设置链代码文件

现在让我们设置 chaincode 文件。

我们将使用从 GitHub 克隆的文件夹结构进行工作。链代码文件位于以下文件夹中:

$GOPATH/src/trade-finance-logistics/chaincode/src/github.com/trade_workflow_v1

您可以按照步骤检查文件夹中的代码文件,也可以创建一个新文件夹并按描述创建代码文件。

  1. 首先,我们需要创建 chaincode 文件

在您喜欢的编辑器中,创建一个名为 tradeWorkflow.go 的文件,并包含以下包和导入语句:

package main

import (
    "fmt"
    "errors"
    "strconv"
    "strings"
    "encoding/json"
    "github.com/hyperledger/fabric/core/chaincode/shim"
    "github.com/hyperledger/fabric/core/chaincode/lib/cid"
    pb "github.com/hyperledger/fabric/protos/peer"
)

在前面的代码片段中,我们可以看到第 4 至 8 行导入了 Go 语言系统包,第 9 至 11 行导入了 shimcidpb Fabric 包。pb 包提供了对 peer protobuf 类型的定义,而 cid 则提供了访问控制函数。在访问控制部分,我们将更详细地了解 CID。

  1. 现在我们需要定义 Chaincode 类型。让我们添加将实现链代码函数的 TradeWorkflowChaincode 类型,如下所示:
type TradeWorkflowChaincode struct {
    testMode bool
}

注意第 2 行中的 testMode 字段。我们将在测试期间使用此字段来绕过访问控制检查。

  1. TradeWorkflowChaincode 是实现 shim.Chaincode 接口所需的。必须实现接口的方法,以使 TradeWorkflowChaincode 成为 shim 包中 Chaincode 类型的有效实现。

  2. Init 方法在将链码安装到区块链网络后调用。每个认可节点只会执行一次,部署其自己的链码实例。此方法可用于初始化、引导和设置链码。Init 方法的默认实现如下代码片段所示。请注意,第 3 行中的方法向标准输出写入一行以报告其调用。第 4 行中,方法返回了对 shim 函数调用的结果。成功的参数值为 nil 表示成功执行并返回空结果,如下所示:

// TradeWorkflowChaincode implementation
func (t *TradeWorkflowChaincode) Init(stub SHIM.ChaincodeStubInterface)         pb.Response {
    fmt.Println("Initializing Trade Workflow")
    return shim.Success(nil)
}

调用链码方法必须返回 pb.Response 对象的一个实例。以下代码片段列出了两个来自 SHIM 包的辅助函数,用于创建响应对象。以下函数将响应序列化为 gRPC protobuf 消息:

// Creates a Response object with the Success status and with argument of a 'payload' to return
// if there is no value to return, the argument 'payload' should be set to 'nil'
func shim.Success(payload []byte)

// creates a Response object with the Error status and with an argument of a message of the error
func shim.Error(msg string)
  1. 现在是移步到调用参数的时候了。在这里,该方法将使用 stub.GetFunctionAndParameters 函数检索调用的参数,并验证是否提供了预期数量的参数。Init 方法希望收到零个参数,因此将账本保持为原样。当 Init 函数被调用时,这是因为链码在账本上升级到新版本。当链码首次安装时,它希望收到包含参与者详情的八个参数,这些将被记录为初始状态。如果提供了不正确数量的参数,该方法将返回错误。验证参数的代码块如下:
_, args := stub.GetFunctionAndParameters()
var err error

// 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 {
 err = errors.New(fmt.Sprintf("Incorrect number of arguments. Expecting 8: {" +
             "Exporter, " +
             "Exporter's Bank, " +
             "Exporter's Account Balance, " +
             "Importer, " +
             "Importer's Bank, " +
             "Importer's Account Balance, " +
             "Carrier, " +
             "Regulatory Authority" +
             "}. Found %d", len(args)))
  return shim.Error(err.Error())
}

正如我们在上述代码片段中所见,当提供了包含参与者姓名和角色的预期参数数量时,该方法会验证和将参数转换为正确的数据类型,并将它们作为初始状态记录到账本中。

在下面的代码片段中,在第 2 和第 7 行,该方法将参数转换为整数。如果转换失败,它返回错误。在第 14 行,通过字符串常量构造了一个字符串数组。这里,我们引用了在 chaincode 文件夹中的 constants.go 文件中定义的词法常量,这些常量代表了初始值将被记录到账本中的键。最后,在第 16 行,对于每个常量,一个记录(资产)被写入到账本中。stub.PutState 函数记录了一个键值对到账本中。

请注意,账本上的数据以字节数组形式存储;我们想要存储在账本上的任何数据都必须首先转换为字节数组,正如您在以下代码片段中所见:

// Type checks
_, err = strconv.Atoi(string(args[2]))
if err != nil {
    fmt.Printf("Exporter's account balance must be an integer. Found %s\n", args[2])
    return shim.Error(err.Error())
}
_, err = strconv.Atoi(string(args[5]))
if err != nil {
    fmt.Printf("Importer's account balance must be an integer. Found %s\n", args[5])
    return shim.Error(err.Error())
}

// Map participant identities to their roles on the ledger
roleKeys := []string{ expKey, ebKey, expBalKey, impKey, ibKey, impBalKey, carKey, raKey }
for i, roleKey := range roleKeys {
    err = stub.PutState(roleKey, []byte(args[i]))
    if err != nil {
        fmt.Errorf("Error recording key %s: %s\n", roleKey, err.Error())
        return shim.Error(err.Error())
    }
}

调用方法

每当查询或修改区块链的状态时,都会调用Invoke方法。

所有对账本上持有的资产的创建读取更新删除CRUD)操作都由Invoke方法封装。

当事务由调用客户端创建时,将调用此方法。当查询状态时(即,检索一个或多个资产但不修改账本的状态时),上下文事务将在客户端接收到Invoke响应后被丢弃。一旦账本已被修改,修改将被记录到事务中。在接收到要记录在账本上的事务的响应后,客户端将提交该事务给一个排序服务。下面的代码片段显示了一个空的Invoke方法:

func (t *TradeWorkflowChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    fmt.Println("TradeWorkflow Invoke")
}

通常,链码的实现将包含多个查询和修改函数。如果这些函数非常简单,可以直接在Invoke方法的主体中实现。然而,更加优雅的解决方案是独立实现每个函数,然后从Invoke方法中调用它们。

SHIM API 提供了几个函数来检索Invoke方法的调用参数。这些列在以下代码片段中。开发人员可以选择参数的含义和顺序;但是,通常情况下,Invoke方法的第一个参数是函数的名称,其后的参数是该函数的参数。

// Returns the first argument as the function name and the rest of the arguments as parameters in a string array.
// The client must pass only arguments of the type string.
func GetFunctionAndParameters() (string, []string)

// Returns all arguments as a single string array.
// The client must pass only arguments of the type string.
func GetStringArgs() []string

// Returns the arguments as an array of byte arrays.
func GetArgs() [][]byte

// Returns the arguments as a single byte array.
func GetArgsSlice() ([]byte, error)

在下面的代码片段中,第 1 行使用stub.GetFunctionAndParameters函数检索调用的参数。从第 3 行开始,一系列if条件将执行与参数一起传递到请求的函数(requestTradeacceptTrade等)。每个这些函数都独立实现其功能。如果请求了一个不存在的函数,该方法将返回一个错误,指示请求的函数不存在,如第 18 行所示:

    function, args := stub.GetFunctionAndParameters()

    if function == "requestTrade" {
        // Importer requests a trade
        return t.requestTrade(stub, creatorOrg, creatorCertIssuer, args)
    } else if function == "acceptTrade" {
        // Exporter accepts a trade
        return t.acceptTrade(stub, creatorOrg, creatorCertIssuer, args)
    } else if function == "requestLC" {
        // Importer requests an L/C
        return t.requestLC(stub, creatorOrg, creatorCertIssuer, args)
    } else if function == "issueLC" {
        // Importer's Bank issues an L/C
        return t.issueLC(stub, creatorOrg, creatorCertIssuer, args)
    } else if function == "acceptLC" {
  ...

  return shim.Error("Invalid invoke function name")

如您所见,Invoke方法是放置任何需要用于提取和验证将由请求的函数使用的参数的共享代码的合适位置。在下一节中,我们将看看访问控制机制,并将一些共享访问控制代码放入Invoke方法中。

访问控制

在深入讨论Chaincode函数的实现之前,我们需要首先定义我们的访问控制机制。

安全且受权限限制的区块链的关键特点是访问控制。在 Fabric 中,成员服务提供商MSP)在启用访问控制方面发挥了关键作用。Fabric 网络的每个组织都可以有一个或多个 MSP 提供者。MSP 实现为证书颁发机构Fabric CA)。有关 Fabric CA 的更多信息,包括其文档,请访问:hyperledger-fabric-ca.readthedocs.io/.

Fabric CA 为网络用户颁发注册证书ecerts)。ecert 代表用户的身份,并在用户提交给 Fabric 时用作签名交易。在调用交易之前,用户必须首先从 Fabric CA 注册并获得 ecert。

Fabric 支持一种基于属性的访问控制ABAC)机制,链码可以使用该机制控制对其功能和数据的访问。ABAC 允许链码根据与用户身份关联的属性做出访问控制决策。拥有 ecert 的用户也可以访问一系列附加属性(即,名称/值对)。

在调用期间,链码将提取属性并做出访问控制决策。我们将在即将到来的章节中更深入地了解 ABAC 机制。

ABAC

在接下来的步骤中,我们将向您展示如何注册用户并创建具有属性的 ecert。然后我们将在链码中检索用户身份和属性,以验证访问控制。然后我们将把这个功能集成到我们的教程链码中。

首先,我们必须使用 Fabric CA 注册一个新用户。在注册过程中,我们必须定义生成 ecert 后将使用的属性。通过运行命令fabric-ca-client register来注册用户。访问控制属性可以使用后缀:ecert添加。

注册用户

这些步骤仅供参考,无法执行。更多信息可以参考 GitHub 存储库github.com/HyperledgerHandsOn/trade-finance-logistics/blob/master/chaincode/abac.md

现在让我们注册一个具有自定义属性名为importer和值为true的用户。请注意,属性的值可以是任何类型,并不仅限于布尔值,如下段所示:

fabric-ca-client register --id.name user1 --id.secret pwd1 --id.type user --id.affiliation ImporterOrgMSP --id.attrs 'importer=true:ecert'

前面的片段显示了注册具有属性importer=true的用户时的命令行。请注意,id.secret的值和其他参数取决于 Fabric CA 配置。

上述命令还可以一次定义多个默认属性,例如:--id.attrsimporter=true:ecert,[email protected]

以下表格包含用户注册期间使用的默认属性:

属性名称 命令行参数 属性值
hf.EnrollmentID (automatic) 身份的注册 ID
hf.Type id.type 身份的类型
hf.Affiliation id.affiliation 身份的从属关系

如果在 ecert 中需要任何先前的属性,则必须首先在用户注册命令中定义它们。例如,以下命令注册user1,其属性为hf.Affiliation=ImporterOrgMSP,该属性将默认复制到 ecert 中:

fabric-ca-client register --id.name user1 --id.secret pwd1 --id.type user --id.affiliation ImporterOrgMSP --id.attrs 'importer=true:ecert,hf.Affiliation=ImporterOrgMSP:ecert'

注册用户

在这里,我们将注册用户并创建 ecert。enrollment.attrs定义了从用户注册中复制到 ecert 的属性。后缀 opt 定义了从注册中复制的这些属性中的哪些是可选的。如果一个或多个非可选属性在用户注册时未定义,则注册将失败。以下命令将注册一个带有属性importer的用户:

fabric-ca-client enroll -u http://user1:pwd1@localhost:7054 --enrollment.attrs "importer,email:opt"

在链码中检索用户身份和属性

在此步骤中,我们将在执行链码期间检索用户的身份。链码可用的 ABAC 功能由客户端身份链码CID)库提供。

提交给链码的每个交易建议都携带着发起者的 ecert – 提交交易的用户。链码通过导入 CID 库并调用带有参数ChaincodeStubInterface的库函数来访问 ecert,即在InitInvoke方法中都收到的参数stub

链码可以使用证书来提取有关调用者的信息,包括:

  • 调用者的 ID

  • 发出调用者证书的**成员服务提供商(MSP)**的唯一 ID

  • 证书的标准属性,如其域名、电子邮件等

  • 存储在证书中与客户端身份相关的 ecert 属性

CID 库提供的函数如下所示:

// Returns the ID associated with the invoking identity. 
// This ID is unique within the MSP (Fabric CA) which issued the identity, however, it is not guaranteed to be unique across all MSPs of the network. 
func GetID() (string, error) 

// Returns the unique ID of the MSP associated with the identity that submitted the transaction. 
// The combination of the MSPID and of the identity ID are guaranteed to be unique across the network. 
func GetMSPID() (string, error) 

// Returns the value of the ecert attribute named `attrName`. 
// If the ecert has the attribute, the `found` returns true and the `value` returns the value of the attribute. 
// If the ecert does not have the attribute, `found` returns false and `value` returns empty string. 
func GetAttributeValue(attrName string) (value string, found bool, err error) 

// The function verifies that the ecert has the attribute named `attrName` and that the attribute value equals to `attrValue`. 
// The function returns nil if there is a match, else, it returns error. 
func AssertAttributeValue(attrName, attrValue string) error 

// Returns the X509 identity certificate. 
// The certificate is an instance of a type Certificate from the library "crypto/x509". 
func GetX509Certificate() (*x509.Certificate, error)  

在以下的代码块中,我们定义了一个名为getTxCreatorInfo的函数,该函数获取调用者的基本身份信息。首先,我们必须导入 CID 和 x509 库,如第 3 和第 4 行所示。第 13 行检索到唯一的 MSPID,第 19 行获取了 X509 证书。然后在第 24 行,我们检索证书的CommonName,其中包含网络中 Fabric CA 的唯一字符串。这两个属性由该函数返回,并在后续的访问控制验证中使用,如以下片段所示:

import ( 
   "fmt" 
   "github.com/hyperledger/fabric/core/chaincode/shim" 
   "github.com/hyperledger/fabric/core/chaincode/lib/cid" 
   "crypto/x509" 
) 

func getTxCreatorInfo(stub shim.ChaincodeStubInterface) (string, string, error) { 
   var mspid string 
   var err error 
   var cert *x509.Certificate 

   mspid, err = cid.GetMSPID(stub) 
   if err != nil { 
         fmt.Printf("Error getting MSP identity: %sn", err.Error()) 
         return "", "", err 
   } 

   cert, err = cid.GetX509Certificate(stub) 
   if err != nil { 
         fmt.Printf("Error getting client certificate: %sn", err.Error()) 
         return "", "", err 
   } 

   return mspid, cert.Issuer.CommonName, nil 
}

现在,我们需要在链码中定义和实现简单的访问控制策略。链码的每个函数只能由特定组织的成员调用;因此,每个链码函数都将验证调用者是否是所需组织的成员。例如,函数requestTrade只能由Importer组织的成员调用。在下面的代码片段中,函数authenticateImporterOrg验证调用者是否是ImporterOrgMSP的成员。然后,将从requestTrade函数调用此函数以执行访问控制。

func authenticateExportingEntityOrg(mspID string, certCN string) bool {
    return (mspID == "ExportingEntityOrgMSP") && (certCN == "ca.exportingentityorg.trade.com")
}
func authenticateExporterOrg(mspID string, certCN string) bool {
return (mspID == "ExporterOrgMSP") && (certCN == "ca.exporterorg.trade.com")
}
func authenticateImporterOrg(mspID string, certCN string) bool {
    return (mspID == "ImporterOrgMSP") && (certCN == "ca.importerorg.trade.com")
}
func authenticateCarrierOrg(mspID string, certCN string) bool {
    return (mspID == "CarrierOrgMSP") && (certCN == "ca.carrierorg.trade.com")
}
func authenticateRegulatorOrg(mspID string, certCN string) bool {
    return (mspID == "RegulatorOrgMSP") && (certCN == "ca.regulatororg.trade.com")
}

下面的代码片段显示了访问控制验证的调用,该验证仅授予ImporterOrgMSP成员访问权限。该函数使用从getTxCreatorInfo函数获取的参数进行调用。

creatorOrg, creatorCertIssuer, err = getTxCreatorInfo(stub)
if !authenticateImporterOrg(creatorOrg, creatorCertIssuer) {
    return shim.Error("Caller not a member of Importer Org. Access denied.")
}

现在,我们需要将身份验证函数放入一个单独的文件accessControlUtils.go中,该文件位于与主tradeWorkflow.go文件相同的目录中。此文件将在编译期间自动导入到主chaincode文件中,因此我们可以引用其中定义的函数。

实现链码函数

到此为止,我们现在拥有链码的基本构建模块。我们有Init方法,用于初始化链码,以及Invoke方法,用于接收来自客户端和访问控制机制的请求。现在,我们需要定义链码的功能。

根据我们的场景,以下表格总结了记录和检索数据以及提供智能合约业务逻辑所需的函数列表。这些表格还定义了组织成员的访问控制定义,以便调用相应的函数。

以下表格说明了链码修改函数,即如何在分类账上记录交易:

函数名称 调用权限 描述
requestTrade 进口商 请求贸易协议
acceptTrade 出口商 接受贸易协议
requestLC 进口商 请求信用证
issueLC 进口商 发行信用证
acceptLC 出口商 接受信用证
requestEL 出口商 请求出口许可证
issueEL 监管机构 发行出口许可证
prepareShipment 出口商 准备装运
acceptShipmentAndIssueBL 承运人 接受装运并发行提单
requestPayment 出口商 请求支付
makePayment 进口商 进行支付
updateShipmentLocation 承运人 更新装运位置

以下表格说明了链码查询函数,即从分类账中检索数据所需的函数:

函数名称 调用权限 描述
getTradeStatus 出口商/出口实体/进口商 获取贸易协议的当前状态
getLCStatus 出口商/出口实体/进口商 获取信用证的当前状态
getELStatus 出口实体/监管机构 获取出口许可证的当前状态
getShipmentLocation 出口商/出口实体/进口商/承运人 获取货物当前位置
getBillOfLading 出口商/出口实体/进口商 获取提货单
getAccountBalance 出口商/出口实体/进口商 获取给定参与者的当前账户余额

定义链码资产

现在我们要定义资产的结构,这些资产将记录在账本上。在 Go 语言中,资产被定义为具有属性名和类型列表的结构类型。定义还需要包含 JSON 属性名,这些属性名将用于将资产序列化为 JSON 对象。在下面的片段中,您将看到我们应用程序中四个资产的定义。请注意,结构体的属性可以封装其他结构体,从而允许创建多级树。

type TradeAgreement struct { 
   Amount                    int               `json:"amount"` 
   DescriptionOfGoods        string            `json:"descriptionOfGoods"` 
   Status                    string            `json:"status"` 
   Payment                   int               `json:"payment"` 
} 

type LetterOfCredit struct { 
   Id                        string            `json:"id"` 
   ExpirationDate            string            `json:"expirationDate"` 
   Beneficiary               string            `json:"beneficiary"` 
   Amount                    int               `json:"amount"` 
   Documents                 []string          `json:"documents"` 
   Status                    string            `json:"status"` 
} 

type ExportLicense struct { 
   Id                        string            `json:"id"` 
   ExpirationDate            string            `json:"expirationDate"` 
   Exporter                  string            `json:"exporter"` 
   Carrier                   string            `json:"carrier"` 
   DescriptionOfGoods        string            `json:"descriptionOfGoods"` 
   Approver                  string            `json:"approver"` 
   Status                    string            `json:"status"` 
} 

type BillOfLading struct { 
   Id                        string            `json:"id"` 
   ExpirationDate            string            `json:"expirationDate"` 
   Exporter                  string            `json:"exporter"` 
   Carrier                   string            `json:"carrier"` 
   DescriptionOfGoods        string            `json:"descriptionOfGoods"` 
   Amount                    int               `json:"amount"` 
   Beneficiary               string            `json:"beneficiary"` 
   SourcePort                string            `json:"sourcePort"` 
   DestinationPort           string            `json:"destinationPort"` 
}  

编写链码函数

在本节中,我们将实现之前查看过的链码函数。为了实现链码函数,我们将使用三个 SHIM API 函数,这些函数将从 Worldstate 中读取资产并记录更改。正如我们已经学到的那样,这些函数的读取和写入分别记录到ReadSetWriteSet中,而这些更改并不会立即影响账本的状态。只有在交易通过验证并被提交到账本之后,更改才会生效。

以下片段显示了一系列资产 API 函数:

// Returns the value of the `key` from the Worldstate. 
// If the key does not exist in the Worldstate the function returns (nil, nil). 
// The function does not read data from the WriteSet and hence uncommitted values modified by PutState are not returned. 
func GetState(key string) ([]byte, error) 

// Records the specified `key` and `value` into the WriteSet. 
// The function does not affect the ledger until the transaction is committed into the ledger. 
func PutState(key string, value []byte) error 

// Marks the the specified `key` as deleted in the WriteSet. 
// The key will be marked as deleted and removed from Worldstate once the transaction is committed into the ledger. 
func DelState(key string) error

创建资产

现在我们可以实现我们的第一个链码函数了,接下来我们将实现一个requestTrade函数,它将创建一个新的交易协议,状态为REQUESTED,然后记录该协议到账本上。

函数的实现如下所示。正如您将看到的,第 9 行我们验证调用者是否是ImporterOrg的成员,并且具有调用该函数的权限。从第 13 行到第 21 行,我们验证并提取参数。在第 23 行,我们创建一个以接收到的参数初始化的新的TradeAgreement实例。正如我们之前学到的,账本以字节数组的形式存储值。因此,在第 24 行我们将TradeAgreement序列化为 JSON 并转换为字节数组。在第 32 行,我们创建一个唯一的键,我们将存储TradeAgreement。最后,在第 37 行,我们使用键和序列化的TradeAgreement以及函数PutState将值存储到WriteSet中。

以下片段说明了requestTrade函数:

func (t *TradeWorkflowChaincode) requestTrade(stub shim.ChaincodeStubInterface, creatorOrg string, creatorCertIssuer string, args []string) pb.Response { 
   var tradeKey string 
   var tradeAgreement *TradeAgreement 
   var tradeAgreementBytes []byte 
   var amount int 
   var err error 

   // Access control: Only an Importer Org member can invoke this transaction 
   if !t.testMode && !authenticateImporterOrg(creatorOrg, creatorCertIssuer) { 
         return shim.Error("Caller not a member of Importer Org. Access denied.") 
   } 

   if len(args) != 3 { 
         err = errors.New(fmt.Sprintf("Incorrect number of arguments. Expecting 3: {ID, Amount, Description of Goods}. Found %d", len(args))) 
         return shim.Error(err.Error()) 
   } 

   amount, err = strconv.Atoi(string(args[1])) 
   if err != nil { 
         return shim.Error(err.Error()) 
   } 

   tradeAgreement = &TradeAgreement{amount, args[2], REQUESTED, 0} 
   tradeAgreementBytes, err = json.Marshal(tradeAgreement) 
   if err != nil { 
         return shim.Error("Error marshaling trade agreement structure") 
   } 

   // Write the state to the ledger 
   tradeKey, err = getTradeKey(stub, args[0]) 
   if err != nil { 
         return shim.Error(err.Error()) 
   } 
   err = stub.PutState(tradeKey, tradeAgreementBytes) 
   if err != nil { 
         return shim.Error(err.Error()) 
   } 
   fmt.Printf("Trade %s request recorded", args[0]) 

   return shim.Success(nil) 
}  

读取和修改资产

当我们实现了创建交易协议的函数之后,我们需要实现一个函数来接受交易协议。该函数将检索协议,将其状态修改为ACCEPTED,并将其放回到账本上。

此函数的实现如下所示。在代码中,我们构造了我们想要检索的贸易协议的唯一的复合键。在第 22 行,我们使用GetState函数检索值。在第 33 行,我们将字节数组反序列化为TradeAgreement结构的实例。在第 41 行,我们修改状态,使其为ACCEPTED;最后,在第 47 行,我们将更新后的值存储在分类账上,如下所示:

func (t *TradeWorkflowChaincode) acceptTrade(stub shim.ChaincodeStubInterface, creatorOrg string, creatorCertIssuer string, args []string) pb.Response { 
   var tradeKey string 
   var tradeAgreement *TradeAgreement 
   var tradeAgreementBytes []byte 
   var err error 

   // 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.") 
   } 

   if len(args) != 1 { 
         err = errors.New(fmt.Sprintf("Incorrect number of arguments. Expecting 1: {ID}. Found %d", len(args))) 
         return shim.Error(err.Error()) 
   } 

   // Get the state from the ledger 
   tradeKey, err = getTradeKey(stub, args[0]) 
   if err != nil { 
         return shim.Error(err.Error()) 
   } 
   tradeAgreementBytes, err = stub.GetState(tradeKey) 
   if err != nil { 
         return shim.Error(err.Error()) 
   } 

   if len(tradeAgreementBytes) == 0 { 
         err = errors.New(fmt.Sprintf("No record found for trade ID %s", args[0])) 
         return shim.Error(err.Error()) 
   } 

   // Unmarshal the JSON 
   err = json.Unmarshal(tradeAgreementBytes, &tradeAgreement) 
   if err != nil { 
         return shim.Error(err.Error()) 
   } 

   if tradeAgreement.Status == ACCEPTED { 
         fmt.Printf("Trade %s already accepted", args[0]) 
   } else { 
         tradeAgreement.Status = ACCEPTED 
         tradeAgreementBytes, err = json.Marshal(tradeAgreement) 
         if err != nil { 
               return shim.Error("Error marshaling trade agreement structure") 
         } 
         // Write the state to the ledger 
         err = stub.PutState(tradeKey, tradeAgreementBytes) 
         if err != nil { 
               return shim.Error(err.Error()) 
         } 
   } 
   fmt.Printf("Trade %s acceptance recordedn", args[0]) 

   return shim.Success(nil) 
}  

主函数

最后但并非最不重要的是,我们将添加main函数:Go 程序的初始点。当链码的实例部署在 peer 上时,会执行main函数以启动链码。

在下面片段的第 2 行中,实例化了链码。 函数shim.Start在第 4 行启动了链码,并向 peer 注册了链码,如下所示:

func main() { 
   twc := new(TradeWorkflowChaincode) 
   twc.testMode = false 
   err := shim.Start(twc) 
   if err != nil { 
         fmt.Printf("Error starting Trade Workflow chaincode: %s", err) 
   } 
} 

测试链码

现在我们可以为我们的链码函数编写单元测试,我们将使用内置的自动化 Go 测试框架。有关更多信息和文档,请访问 Go 的官方网站:golang.org/pkg/testing/

框架自动寻找并执行以下签名的函数:

 func TestFname(*testing.T)

函数名Fname是一个任意的名称,必须以大写字母开头。

请注意,包含单元测试的测试套件文件必须以后缀_test.go结束;因此,我们的测试套件文件将被命名为tradeWorkflow_test.go,并放置在与我们的chaincode文件相同的目录中。test函数的第一个参数是类型为T的,它提供了用于管理测试状态并支持格式化测试日志的函数。测试的输出被写入标准输出,可以在终端中检查。

SHIM 模拟

SHIM 包提供了一个全面的模拟模型,可用于测试链码。在我们的单元测试中,我们将使用MockStub类型,它为单元测试链码提供了ChaincodeStubInterface的实现。

测试Init方法

首先,我们需要定义一个函数,用于调用Init方法。该函数将接收对MockStub的引用,以及一个传递给Init方法的参数数组。在以下代码的第 2 行,使用接收到的参数调用链码函数Init,然后在第 3 行进行验证。

以下代码片段演示了Init方法的调用:

 func checkInit(t *testing.T, stub *shim.MockStub, args [][]byte) { 
   res := stub.MockInit("1", args) 
   if res.Status != shim.OK { 
         fmt.Println("Init failed", string(res.Message)) 
         t.FailNow() 
   } 
} 

我们将定义一个函数,用于准备Init函数参数的默认值数组,如下所示:

func getInitArguments() [][]byte { 
   return [][]byte{[]byte("init"), 
               []byte("LumberInc"), 
               []byte("LumberBank"), 
               []byte("100000"), 
               []byte("WoodenToys"), 
               []byte("ToyBank"), 
               []byte("200000"),
               []byte("UniversalFreight"), 
               []byte("ForestryDepartment")} 
} 

现在我们将定义Init函数的测试,如下所示。测试首先创建链码的一个实例,然后将模式设置为测试,最后为链码创建一个新的MockStub。在第 7 行,调用checkInit函数并执行Init函数。最后,从第 9 行开始,我们将验证分类账的状态,如下所示:

func TestTradeWorkflow_Init(t *testing.T) { 
   scc := new(TradeWorkflowChaincode) 
   scc.testMode = true 
   stub := shim.NewMockStub("Trade Workflow", scc) 

   // Init 
   checkInit(t, stub, getInitArguments()) 

   checkState(t, stub, "Exporter", EXPORTER) 
   checkState(t, stub, "ExportersBank", EXPBANK) 
   checkState(t, stub, "ExportersAccountBalance", strconv.Itoa(EXPBALANCE)) 
   checkState(t, stub, "Importer", IMPORTER) 
   checkState(t, stub, "ImportersBank", IMPBANK) 
   checkState(t, stub, "ImportersAccountBalance", strconv.Itoa(IMPBALANCE)) 
   checkState(t, stub, "Carrier", CARRIER) 
   checkState(t, stub, "RegulatoryAuthority", REGAUTH) 
}

接下来,我们将通过checkState函数验证每个键的状态是否符合预期,如以下代码块所示:

func checkState(t *testing.T, stub *shim.MockStub, name string, value string) { 
  bytes := stub.State[name] 
  if bytes == nil { 
    fmt.Println("State", name, "failed to get value") 
    t.FailNow() 
  } 
  if string(bytes) != value {
    fmt.Println("State value", name, "was", string(bytes), "and not", value, "as expected")
    t.FailNow()
  }
} 

测试调用方法

现在是定义Invoke函数的测试的时候了。在以下代码块的第 7 行,调用checkInit来初始化总账,然后在第 13 行调用checkInvoke,调用requestTrade函数。requestTrade函数创建一个新的贸易资产并将其存储在总账上。在第 15 和 16 行创建并序列化一个新的TradeAgreement,然后在第 17 行计算一个新的复合键。最后,在第 18 行,验证键的状态是否与序列化的值相匹配。

此外,正如前面所述,我们的链码包含一系列函数,这些函数一起定义了贸易工作流程。我们将在测试中将这些函数的调用链接成一个序列,以验证整个工作流程。整个函数的代码可以在位于chaincode文件夹中的测试文件中找到。

func TestTradeWorkflow_Agreement(t *testing.T) { 
   scc := new(TradeWorkflowChaincode) 
   scc.testMode = true 
   stub := shim.NewMockStub("Trade Workflow", scc) 

   // Init 
   checkInit(t, stub, getInitArguments()) 

   // Invoke 'requestTrade' 
   tradeID := "2ks89j9" 
   amount := 50000 
   descGoods := "Wood for Toys" 
   checkInvoke(t, stub, [][]byte{[]byte("requestTrade"), []byte(tradeID), []byte(strconv.Itoa(amount)), []byte(descGoods)}) 

   tradeAgreement := &TradeAgreement{amount, descGoods, REQUESTED, 0} 
   tradeAgreementBytes, _ := json.Marshal(tradeAgreement) 
   tradeKey, _ := stub.CreateCompositeKey("Trade", []string{tradeID}) 
   checkState(t, stub, tradeKey, string(tradeAgreementBytes)) 
   ... 
}

下面的代码段显示了checkInvoke函数。

func checkInvoke(t *testing.T, stub *shim.MockStub, args [][]byte) { 
   res := stub.MockInvoke("1", args) 
   if res.Status != shim.OK { 
         fmt.Println("Invoke", args, "failed", string(res.Message)) 
         t.FailNow() 
   } 
}

运行测试

现在我们准备好运行我们的测试了!go test命令将执行在tradeWorkflow_test.go文件中找到的所有测试。该文件包含一系列测试,验证了我们工作流中定义的函数。

现在让我们使用以下命令在终端中运行测试:

$ cd $GOPATH/src/trade-finance-logistics/chaincode/src/github.com/trade_workflow_v1 
$ go test 

前面的命令应该生成以下输出:

Initializing Trade Workflow 
Exporter: LumberInc 
Exporter's Bank: LumberBank 
Exporter's Account Balance: 100000 
Importer: WoodenToys 
Importer's Bank: ToyBank 
Importer's Account Balance: 200000 
Carrier: UniversalFreight 
Regulatory Authority: ForestryDepartment 
... 
Amount paid thus far for trade 2ks89j9 = 25000; total required = 50000 
Payment request for trade 2ks89j9 recorded 
TradeWorkflow Invoke 
TradeWorkflow Invoke 
Query Response:{"Balance":"150000"} 
TradeWorkflow Invoke 
Query Response:{"Balance":"150000"} 
PASS 
ok       trade-finance-logistics/chaincode/src/github.com/trade_workflow_v1      0.036s 

链码设计主题

复合键

我们经常需要在总帐上存储一个类型的多个实例,比如多个贸易协议、信用证等等。在这种情况下,这些实例的键通常将由多个属性的组合构造而成,例如"Trade" + ID, yielding ["Trade1","Trade2", ...]。实例的键可以在代码中自定义,或者在 SHIM 中提供 API 函数来构造实例的复合键(换句话说,基于几个属性的唯一键)。这些函数简化了复合键的构造。复合键可以像普通字符串键一样使用PutState()GetState()函数来记录和检索值。

以下代码段显示了一系列创建和使用复合键的函数:

// The function creates a key by combining the attributes into a single string. 
// The arguments must be valid utf8 strings and must not contain U+0000 (nil byte) and U+10FFFF charactres. 
func CreateCompositeKey(objectType string, attributes []string) (string, error) 

// The function splits the compositeKey into attributes from which the key was formed. 
// This function is useful for extracting attributes from keys returned by range queries. 
func SplitCompositeKey(compositeKey string) (string, []string, error) 

在下面的代码段中,我们可以看到一个名为getTradeKey的函数,它通过将关键字Trade与贸易的 ID 组合构造了一个唯一的复合键:

func getTradeKey(stub shim.ChaincodeStubInterface, tradeID string) (string, error) { 
   tradeKey, err := stub.CreateCompositeKey("Trade", []string{tradeID}) 
   if err != nil { 
         return "", err 
   } else { 
         return tradeKey, nil 
   } 
}

在更复杂的情况下,键可以由多个属性构造。复合键还允许您根据键的组件在范围查询中搜索资产。我们将在接下来的部分更详细地探讨搜索。

范围查询

除了使用唯一键检索资产之外,SHIM 还提供 API 函数来根据范围条件检索一系列资产。此外,可以对复合键进行建模,以便查询多个键的组件。

范围函数返回与查询条件匹配的一组键的迭代器(StateQueryIteratorInterface)。 返回的键按字典顺序排列。 迭代器必须通过调用Close()函数关闭。 此外,当复合键具有多个属性时,范围查询函数GetStateByPartialCompositeKey()可用于搜索匹配部分属性的键。

例如,由TradeIdPaymentId组成的支付密钥可以在与特定TradeId相关联的所有支付中进行搜索,如下片段所示:

 // Returns an iterator over all keys between the startKey (inclusive) and endKey (exclusive). 
// To query from start or end of the range, the startKey and endKey can be an empty. 
func GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) 

// Returns an iterator over all composite keys whose prefix matches the given partial composite key. 
// Same rules as for arguments of CreateCompositeKey function apply. 
func GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error) 

我们还可以使用以下查询搜索 ID 范围在 1-100 之间的所有贸易协议:

startKey, err = getTradeKey(stub, "1") 
endKey, err = getTradeKey(stub, "100") 

keysIterator, err := stub.GetStateByRange(startKey, endKey) 
if err != nil { 
    return shim.Error(fmt.Printf("Error accessing state: %s", err)) 
} 

defer keysIterator.Close() 

var keys []string 
for keysIterator.HasNext() { 
    key, _, err := keysIterator.Next() 
    if err != nil { 
        return shim.Error(fmt.Printf("keys operation failed. Error accessing state: %s", err)) 
    } 
    keys = append(keys, key) 
}

状态查询和 CouchDB

默认情况下,Fabric 使用 LevelDB 作为 Worldstate 的存储。 Fabric 还提供了配置对等方将 Worldstate 存储在 CouchDB 中的选项。 当资产以 JSON 文档的形式存储时,CouchDB 允许您根据资产状态执行复杂的查询。

查询采用本机 CouchDB 声明性 JSON 查询语法格式化。 此语法的当前版本可在以下链接找到:docs.couchdb.org/en/2.1.1/api/database/find.html.

Fabric 将查询转发到 CouchDB 并返回一个迭代器(StateQueryIteratorInterface()),该迭代器可用于迭代结果集。 基于状态的查询函数的声明如下所示:

func GetQueryResult(query string) (StateQueryIteratorInterface, error)

在下面的代码片段中,我们可以看到一个基于状态的查询,用于所有状态为ACCEPTED且收到的付款超过 1000 的贸易协议。 然后执行查询,并将找到的文档写入终端,如下所示:

// CouchDB query definition
queryString :=
`{
    "selector": {
            "status": "ACCEPTED"
            "payment": {
                    "$gt": 1000
            }
    }
}`

fmt.Printf("queryString:\n%s\n", queryString)

// Invoke query
resultsIterator, err := stub.GetQueryResult(queryString)
if err != nil {
    return nil, err
}
defer resultsIterator.Close()

var buffer bytes.Buffer
buffer.WriteString("[")

// Iterate through all returned assets
bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
    queryResponse, err := resultsIterator.Next()
    if err != nil {
        return nil, err
    }
    if bArrayMemberAlreadyWritten == true {
        buffer.WriteString(",")
    }
    buffer.WriteString("{\"Key\":")
    buffer.WriteString("\"")
    buffer.WriteString(queryResponse.Key)
    buffer.WriteString("\"")

    buffer.WriteString(", \"Record\":")
    buffer.WriteString(string(queryResponse.Value))
    buffer.WriteString("}")
    bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")

fmt.Printf("queryResult:\n%s\n", buffer.String())

请注意,与键的查询不同,对状态的查询不会记录到交易的ReadSet中。 因此,交易的验证实际上无法验证在执行和提交交易之间 Worldstate 的更改。 因此,链码设计必须考虑到这一点; 如果查询基于预期的调用序列,那么无效的交易可能会出现。

索引

在大型数据集上执行查询是一项计算复杂的任务。 Fabric 提供了在 CouchDB 托管的 Worldstate 上定义索引以提高效率的机制。 请注意,索引也是查询中排序操作所必需的。

索引在一个扩展名为*.json的单独文件中以 JSON 格式定义。 格式的完整定义可在以下链接找到:docs.couchdb.org/en/2.1.1/api/database/find.html#db-index

以下代码片段说明了一个与我们之前查看的贸易协议查询匹配的索引:

 { 
  "index": { 
    "fields": [ 
      "status", 
      "payment" 
    ] 
  }, 
  "name": "index_sp", 
  "type": "json" 
}  

在这里,索引文件被放置到文件夹/META-INF/statedb/couchdb/indexes中。在编译期间,索引与链码一起打包。在对等体上安装和实例化链码后,索引会自动部署到世界状态并用于查询。

读集(ReadSet)和写集(WriteSet)

当接收到来自客户端的事务调用消息时,背书节点会执行一个事务。执行会在对等节点的世界状态上下文中调用链码,并将其数据的所有读取和写入记录到ReadSetWriteSet中。

交易的WriteSet包含了在链码执行期间被修改的键值对列表。当修改键的值时(即记录新键值对或使用新值更新现有键),WriteSet会包含更新后的键值对。

当键被删除时,WriteSet会包含具有标记键为已删除的属性的键。如果在链码执行期间多次修改单个键,则WriteSet会包含最新修改的值。

交易的ReadSet包含了在链码执行期间访问的键及其版本的列表。键的版本号由区块号和区块内事务号的组合派生而来。这种设计使得数据的高效搜索和处理成为可能。交易的另一部分包含了关于范围查询及其结果的信息。请记住,当链码读取键的值时,会返回账本中最新提交的值。

如果链码执行期间引入的修改被存储在WriteSet中,当链码读取在执行期间被修改的键时,会返回已提交而非修改后的值。因此,如果后续需要修改后的值,则必须实现链码以保留并使用正确的值。

一个交易的ReadSetWriteSet的示例如下:

{
  "rwset": {
    "reads": [
      {
        "key": "key1",
        "version": {
          "block_num": {
            "low": 9546,
            "high": 0,
            "unsigned": true
          },
          "tx_num": {
            "low": 0,
            "high": 0,
            "unsigned": true
          }
        }
      }
    ],
    "range_queries_info": [],
    "writes": [
      {
        "key": "key1",
        "is_delete": false,
        "value": "value1"
      },
      {
        "key": "key2",
        "is_delete": true
      }
    ]
  }
}

多版本并发控制

Fabric 使用多版本并发控制MVCC)机制来确保账本的一致性并防止双重支付。双重支付攻击旨在通过引入使用或多次修改同一资源的事务来利用系统中的缺陷,比如在加密货币网络中多次花费同一枚硬币。键碰撞是另一种可能发生的问题类型,它可能会在并行客户端提交的事务处理中尝试同时修改相同的键/值对。

此外,由于 Fabric 的去中心化架构,事务执行顺序可以在不同的 Fabric 组件(包括背书者,排序者和提交者)上有不同的排序和提交方式,从而在交易计算和提交之间引入延迟,其中可能发生键冲突。去中心化还使网络容易受到客户端故意或无意地修改交易顺序的潜在问题和攻击的影响。

为确保一致性,像数据库这样的计算机系统通常使用锁定机制。然而,在 Fabric 中无法使用这种集中式方法。同时,也值得注意的是,锁定有时可能会导致性能下降。

为了应对这一点,Fabric 使用了存储在总账簿上的键的版本系统。版本系统的目标是确保交易按照不引入不一致性的顺序被排序和提交到总账簿中。当在提交对等方收到一个块时,会验证块中的每笔交易。该算法会检查ReadSet中的键及其版本;如果ReadSet中每个键的版本与世界状态中相同键的版本,或同一块中之前的交易的版本相匹配,则交易被视为有效。换句话说,该算法验证执行交易期间从世界状态读取的任何数据是否已发生更改。

如果一个事务包含范围查询,这些查询也将得到验证。对于每个范围查询,算法会检查在链码执行期间执行查询的结果是否与之前完全相同,或者是否发生了任何修改。

未通过此验证的交易将在总账簿中标记为无效,并且它们所引入的更改不会映射到世界状态中。需要注意的是,由于总账簿是不可修改的,这些交易会保留在总账簿上。

如果交易通过了验证,WriteSet将被映射到世界状态。交易修改的每个键都会在世界状态中设置为WriteSet中指定的新值,并且该键在世界状态中的版本会设置为从交易中导出的版本。通过这种方式,任何重复支出等不一致性将被防止。同时,在可能发生键冲突的情况下,链码设计必须考虑 MVCC 的行为。针对键冲突和 MVCC,存在多种公认的解决策略,如分割资产、使用多个键、事务排队等。

日志输出

日志记录是系统代码的重要组成部分,它使得可以分析和检测运行时问题。

Fabric 中的日志记录是基于标准的 Go 日志包github.com/op/go-logging。日志机制提供基于严重性的日志控制和消息的漂亮打印装饰。日志级别按严重性递减的顺序定义,如下所示:

CRITICAL | ERROR | WARNING | NOTICE | INFO | DEBUG 

所有组件生成的日志消息都会组合并写入标准错误文件(stderr)。可以通过对对等体和模块的配置以及 chaincode 的代码来控制日志记录。

配置

对等体日志的默认配置设置为级别 INFO,但可以通过以下方式控制此级别:

  1. 命令行选项日志级别。此选项覆盖默认配置,如下所示:
peer node start --logging-level=error  

请注意,通过命令行选项可以配置任何模块或 chaincode,如下所示:

 peer node start --logging-level=chaincode=error:main=info
  1. 默认的日志级别也可以通过environment变量CORE_LOGGING_LEVEL来定义,默认配置如下所示:
peer0.org1.example.com:
    environment:
        - CORE_LOGGING_LEVEL=error
  1. core.yml文件中的一个配置属性,定义了网络的配置,也可以与以下代码一起使用:
logging:
    level: info
  1. core.yml 文件还允许您配置特定模块的日志级别,例如chaincode模块或消息格式,如下节选所示:
 chaincode: 
   logging: 
         level:  error 
         shim:   warning  

关于各种配置选项的详细信息包含在core.yml文件的注释中。

日志 API

SHIM 包提供了 API,供 chaincode 创建和管理日志对象。这些对象生成的日志与对等体日志集成。

chaincode 可以创建和使用任意数量的日志对象。每个日志对象必须有一个唯一的名称,用于在输出中添加日志记录的前缀并区分不同日志对象和 SHIM 的记录。(请记住,日志对象名称 SHIM API 是保留的,不应在 chaincode 中使用。)每个日志对象都设置了一个日志严重级别,在该级别下记录日志将被发送到输出中。具有严重级别CRITICAL的日志记录始终出现在输出中。以下节选列出了在 chaincode 中创建和管理日志对象的 API 函数。

// Creates a new logging object. 
func NewLogger(name string) *ChaincodeLogger 

// Converts a case-insensitive string representing a logging level into an element of LoggingLevel enumeration type. 
// This function is used to convert constants of standard GO logging levels (i.e. CRITICAL, ERROR, WARNING, NOTICE, INFO or DEBUG) into the shim's enumeration LoggingLevel type (i.e. LogDebug, LogInfo, LogNotice, LogWarning, LogError, LogCritical). 
func LogLevel(levelString string) (LoggingLevel, error) 

// Sets the logging level of the logging object. 
func (c *ChaincodeLogger) SetLevel(level LoggingLevel) 

// Returns true if the logging object will generate logs at the given level. 
func (c *ChaincodeLogger) IsEnabledFor(level LoggingLevel) bool 

日志对象ChaincodeLogger提供了每个严重级别的日志记录函数。以下是ChaincodeLogger的函数列表。

func (c *ChaincodeLogger) Debug(args ...interface{}) 
func (c *ChaincodeLogger) Debugf(format string, args ...interface{}) 
func (c *ChaincodeLogger) Info(args ...interface{}) 
func (c *ChaincodeLogger) Infof(format string, args ...interface{}) 
func (c *ChaincodeLogger) Notice(args ...interface{}) 
func (c *ChaincodeLogger) Noticef(format string, args ...interface{}) 
func (c *ChaincodeLogger) Warning(args ...interface{}) 
func (c *ChaincodeLogger) Warningf(format string, args ...interface{}) 
func (c *ChaincodeLogger) Error(args ...interface{}) 
func (c *ChaincodeLogger) Errorf(format string, args ...interface{}) 
func (c *ChaincodeLogger) Critical(args ...interface{}) 
func (c *ChaincodeLogger) Criticalf(format string, args ...interface{}) 

记录的默认格式由 SHIM 的配置定义,该配置在输入参数的打印表示之间添加空格。对于每个严重级别,日志对象还提供了一个带有后缀f的附加函数,这些函数允许您使用参数format来控制输出的格式。

由日志对象生成的输出模板如下:

[timestamp] [logger name] [severity level] printed arguments 

所有日志对象和 SHIM 的输出都会合并并发送到标准错误(stderr)中。

以下代码块说明了如何创建和使用日志对象的示例:

var logger = shim.NewLogger("tradeWorkflow") 
logger.SetLevel(shim.LogDebug) 

_, args := stub.GetFunctionAndParameters() 
logger.Debugf("Function: %s(%s)", "requestTrade", strings.Join(args, ",")) 

if !authenticateImporterOrg(creatorOrg, creatorCertIssuer) { 
   logger.Info("Caller not a member of Importer Org. Access denied:", creatorOrg, creatorCertIssuer) 
} 

SHIM 日志级别

链码还可以通过使用 API 函数 SetLoggingLevel 直接控制其 SHIM 的日志记录严重级别,如下所示:

logLevel, _ := shim.LogLevel(os.Getenv("TW_SHIM_LOGGING_LEVEL"))
shim.SetLoggingLevel(logLevel)

Stdout 和 stderr

除了由 SHIM API 提供并与对等节点集成的日志记录机制外,在开发阶段,链码可以使用标准输出文件。链码作为一个独立的进程执行,因此可以使用标准输出(stdout)和标准错误(stderr)文件来使用标准的 Go 打印函数记录输出(例如,fmt.Printf(...)os.Stdout)。默认情况下,在 Dev 模式下启动链码进程时,标准输出可用。

在生产环境中,当链码进程由对等节点管理时,出于安全原因,标准输出被禁用。当需要时,可以通过设置对等节点的配置变量 CORE_VM_DOCKER_ATTACHSTDOUT 来启用它。然后,链码的输出将与对等节点的输出合并。请注意,这些输出仅应用于调试目的,不应在生产环境中启用。

以下片段说明了其他 SHIM API 函数:

peer0.org1.example.com: 
   environment: 
         - CORE_VM_DOCKER_ATTACHSTDOUT=true 

列表 4.1:在 docker-compose 文件中启用对等节点上链码标准输出文件。

其他 SHIM API 函数

本节我们提供了剩余的适用于链码的 SHIM API 函数的概述。

 // Returns an unique Id of the transaction proposal. 
func GetTxID() string 

// Returns an Id of the channel the transaction proposal was sent to. 
func GetChannelID() string 

// Calls an Invoke function on a specified chaincode, in the context of the current transaction. 
// If the invoked chaincode is on the same channel, the ReadSet and WriteSet will be added into the same transaction. 
// If the invoked chaincode is on a different channel, the invocation can be used only as a query. 
func InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response 

// Returns a list of historical states, timestamps and transactions ids. 
func GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) 

// Returns the identity of the user submitting the transaction proposal. 
func GetCreator() ([]byte, error) 

// Returns a map of fields containing cryptographic material which may be used to implement custom privacy layer in the chaincode. 
func GetTransient() (map[string][]byte, error) 

// Returns data which can be used to enforce a link between application data and the transaction proposal. 
func GetBinding() ([]byte, error) 

// Returns data produced by peer decorators which modified the chaincode input. 
func GetDecorations() map[string][]byte 

// Returns data elements of a transaction proposal. 
func GetSignedProposal() (*pb.SignedProposal, error) 

// Returns a timestamp of the transaction creation by the client. The timestamp is consistent across all endorsers. 
func GetTxTimestamp() (*timestamp.Timestamp, error) 

// Sets an event attached to the transaction proposal response. This event will be be included in the block and ledger. 
func SetEvent(name string, payload []byte) error  

总结

设计和实现一个功能良好的链码是一项复杂的软件工程任务,它需要对 Fabric 架构、API 函数以及 GO 语言的知识,以及对业务需求的正确实现。

在本章中,我们逐步学习了如何启动适用于链码实现和测试的开发模式的区块链网络,以及如何使用 CLI 部署和调用链码。然后我们学习了如何实现我们场景的链码。我们通过 InitInvoke 函数探索了链码如何接收来自客户端的请求,探索了访问控制机制以及开发人员可用于实现链码功能的各种 API。

最后,我们学习了如何测试链码以及如何将日志功能集成到代码中。为了为下一章做好准备,现在应该使用 ./trade.sh down -d true 停止网络。

第五章:暴露网络资产和交易

如果您已经走到这一步,恭喜您!您已经建立了区块链应用程序的核心以及直接读取、更重要的是操纵您网络的记录系统的智能合约。但是,您还没有完成。正如您所能想象的那样,合约是一段敏感的代码,必须受到不当使用或篡改的保护。

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

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

本章将涵盖以下主题:

  • 构建完整的应用程序

  • 将应用程序与现有系统和流程集成

构建完整的应用程序

在本节中,您将学习如何围绕核心智能合约构建一个完整的应用程序,该应用程序可以被已经加入一起形成网络的商业实体方便地使用。我们将从回顾 Hyperledger Fabric 交易流程开始,以提醒读者用户(或客户端)的角度,一个区块链应用程序是如何做的(以及如何做的)。通过代码示例,我们将向您展示如何根据商业实体的需求构建、设计和组织网络,创建适当的配置,并完成从开始到结束的区块链交易的不同阶段。在此过程结束时,读者将了解如何设计一个 Fabric 应用程序,并通过一个简单的 web 界面展示其功能。在本章开始时,我们唯一需要拥有的资产是合约或链代码,它是使用 Go 编程(请参阅 第四章使用 Golang 设计数据和交易模型)手工开发的。

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

Hyperledger Fabric 应用的性质

在之前的章节中,我们看到 Hyperledger Fabric 可以被视为一个分布式事务处理系统,具有分阶段的操作流水线,这些操作最终可能导致由网络对等方维护的共享复制分类帐的状态发生变化。对于开发者来说,区块链应用程序是一组过程,通过这些过程,用户可以向智能合约提交事务,或者从智能合约中读取状态。在底层,开发者必须将用户请求引导到事务流水线的不同阶段,并提取结果以在流程结束时提供反馈。基本上,无论合约是手工实现的(参见 第四章使用 Golang 设计数据和事务模型)还是使用 Hyperledger Composer 实现的(参见 第六章业务网络),应用程序开发者的工作都是在智能合约周围实现一个或多个包装层。

以智能合约(或资产实体模型)为核心开发的应用可以被视为具有一组视图或服务 API 的事务处理数据库应用程序。然而,开发人员必须牢记每个 Hyperledger Fabric 事务都是异步的,也就是说,事务的结果在提交的通信会话中不会立即可用。这是因为,正如我们在之前的章节中所看到的,一个事务必须通过共识被网络中的对等方集体批准。因此,共识可能需要花费不确定的时间,并且事务结果的通信被设计为发布/订阅机制。以下图表从开发者的角度说明了区块链应用程序和事务流水线:

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

在接下来的部分中,将详细描述图中提到的操作,并将其映射到特定的 Fabric 机制。

应用程序和事务阶段

创建应用程序的第一步是实例化区块链,或者说共享分类帐本身。在 Fabric 的术语中,区块链的一个实例被称为通道,因此区块链应用程序的第一步是创建一个通道,并使用通道的创世区块引导网络排序服务。

下一步是对等网络的初始化,所有被选中运行应用程序的对等节点必须加入通道,这个过程允许每个对等方维护一个分类帐的副本,该副本被初始化为一个空白的键值存储。加入通道的每个对等方都将拥有分类帐提交特权,并可以参与八卦协议以与其他对等方同步分类帐状态。

在对等网络创建完成后,将在该网络上安装智能合约。在此之前连接到通道的一部分对等方将被选中来运行智能合约;换句话说,它们将拥有背书特权。合约代码将部署到这些对等方并构建以进行后续操作。如你所知,到这一步,合约在 Fabric 术语中被称为链码,本章的其余部分将使用这个术语。

一旦链码被安装在背书对等方上,将根据嵌入其中的逻辑进行初始化(参见第四章使用 Golang 设计数据和交易模型,以获取示例)。

到这一点为止,除非在前面的一个或多个步骤中出现了问题,否则应用程序已经启动并运行。现在,可以将交易发送到链码,以更新分类帐的状态(调用)或读取分类帐状态(查询)在应用程序的生命周期内。

应用可能随着时间的推移而发生变化或演化,需要执行一些未在图 5.1:区块链应用创建和运行阶段中捕获的特殊操作。这些将在第九章中描述,区块链网络中的生活

在标题为构建应用及其后面的部分,我们将展示如何围绕第四章中开发的链码构建贸易应用,使用适当的代码和说明。

应用模型和架构

编写 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:区块链应用创建和运行阶段中加以说明,以 Fabric 术语和使用 Fabric 术语来表示:

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

Fabric 对等方、排序器和 CA(或 MSP)使用 gRPC(grpc.io/)进行通信,以及由对等方生成的用于运行链代码的进程(该进程实际上是一个 Docker 容器)。该进程导出一个实现 JSON RPC 2.0 规范(www.jsonrpc.org/specification)的服务端点,用于通道和链代码操作。我们可以编写一个直接使用服务规范与链代码通信的包装应用程序,但然后我们将不得不编写逻辑来解析和解释负载。由于 Fabric 平台及其规范可能会在未来发生变化,因此这不一定是编写应用程序的最佳和最可维护的方式,特别是对于生产目的而言。幸运的是,Hyperledger Fabric 提供了两种不同的方式来运行链代码操作,同时隐藏接口规范和通信协议的细节:

  • 命令行界面CLI):Fabric 提供了可以从终端运行的命令,用于执行图 5.3:区块链应用创建和运行阶段中指示的各种操作。运行这些命令的工具是peer,它在下载 Fabric 源代码并构建它(使用make或只是make peer)时生成。可以使用不同的开关与此命令一起使用以执行不同的通道和链代码操作,在本节中您将看到一些示例。

  • 软件开发工具包SDK):Hyperledger 提供了一个工具包和一组库,用于轻松开发多种语言(如 Node.js、Java、Go 和 Python)的应用程序,以包装通道和链码操作。这些 SDK 还提供了与 MSPs 或 Fabric CA 实例进行交互的功能。

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

工具和依赖项的安装

我们将展示如何在我们的中间件中构建的函数可以在代码库的中间件文件夹中找到。

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

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

  1. 安装 Node.js (nodejs.org/en/download/) 和 npm (www.npmjs.com/get-npm)。

  2. 安装 fabric-clientfabric-ca-client npm 库:

    • 您可以从npm注册表中安装这些包,可以手动运行 npm install <package-name> 或者通过在 package.json 文件中设置名称和版本。例如,中间件文件夹中的 package.json 在依赖项部分包含以下条目:

      • fabric-ca-client: ¹.1.0

      • fabric-client: ¹.1.0

  3. 这告诉npm安装这两个包的 1.1.0 版本:

    • 或者,您可以克隆 Fabric SDK node (github.com/hyperledger/fabric-sdk-node/) 源代码库,并按如下方式本地导入这两个库:

      • fabric-clientfabric-ca-client 文件夹中运行 npm install

      • 安装这些包作为依赖项,可以手动在中间件/package.json文件中指定路径,也可以使用npm链接命令将符号链接添加到中间件/node_modules中的包中

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

依赖项的安装

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

安装依赖项和配置中间件以进行常规操作的更干净的方法是使用 Makefile 进行自动构建。您只需在 middleware 文件夹中运行 make;有关设置和构建您的开发和测试环境的更多详细信息,请参阅 第八章 区块链网络中的灵活性

创建和运行中间件

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

网络配置

编写中间件的第一步是收集所有必要的配置信息,以识别并连接到我们在上一节中创建和启动的网络的各个元素。在 JavaScript 中编写代码时,将这些配置表示为 JSON 格式是很有用的。在我们的示例代码中,config.json 文件用于此目的。此文件包含一个网络的描述,其属性包含在 trade-network 对象中。此对象的每个属性描述了网络中每个唯一组织的配置,除了一个称为 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 中的主机名仅指向 localhost,因为这是我们所有网络元素容器运行的地方。

支持政策

下一步是为我们的链码制定一个认可策略,该策略将在实例化期间提交到分类帐。该认可策略规定了需要多少个角色和组织的对等方认可分类帐承诺事务(或调用)。在示例代码中,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 属性中指定,并且引用了四个对等组织的成员(或普通)用户,以及订购者组织的管理员用户。此处的策略属性声明,需要从每个四个对等组织的成员那里获得认可;总共需要四个签名。

constants.js中,变量TRANSACTION_ENDORSEMENT_POLICY默认设置为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启动时必须创建他们。默认情况下,管理用户被赋予 IDadmin和密码adminpw,这是我们在本节练习中将要使用的。我们创建和启动的网络使用这些默认值,读者可以在fabric-ca-servernetwork/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. 如果凭据不存在,则管理员需要注册或登录到 Fabric CA 服务器,并获取他们的凭据(私钥和注册证书)

  3. 由管理用户注册另一个具有给定 ID 的用户,并指定角色和 affiliations 与 Fabric CA 服务器

  4. 使用注册时返回的秘钥,enroll 新用户并获得该用户的凭据

  5. 将凭据保存到本地存储

可以在clientUtils.js中找到相关示例代码,以下代码片段大多来自getUserMember函数,该函数使用管理员凭据,需要用户必须 enrolled 的组织名称/ID。还需要传递一个客户端的 handle(fabric-client的实例,或一个客户端对象)给该函数:

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)。enroll 函数允许管理员使用 MSP 登录,并获取 enrollment 密钥和证书。

现在我们已经获取了管理员用户的句柄,形式上是 member 对象,我们可以使用它来为用户 ID(用户名表示)enroll 一个新用户,如下所示:

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();
})

在注册过程中,我们可以指定用户的角色,上面的代码中是客户端,允许用户名提交调用和查询到链码。此处指定的 affiliation 是组织内的一个细分,它在 Fabric CA 服务器的配置中指定(hyperledger-fabric-ca.readthedocs.io/en/latest/serverconfig.html)(更新此配置留给读者作为练习;在这里,我们将使用默认的 affiliation)。使用返回的秘钥,用户名现在已经与服务器 enrolled,并且其密钥和 enrollment 证书被保存。

调用client.setUserContext将该用户与客户端句柄关联,client.saveUserToStateStore将用户的凭据保存到我们在文件系统上的本地存储。

与获取管理员用户句柄相似的函数还有getAdmingetMember,也在clientUtils.js中定义。前者检索使用cryptogen创建的管理员用户凭据,而后者动态创建一个新的admin member。

创建一个通道

要创建我们的交易通道,我们首先需要实例化一个fabric-client实例,并使用config.json中的配置获得一个到 orderer 的 handle(参见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']
  }
);

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

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 类似于 getSubmitter,返回一个与订购者组织的 admin 用户关联的句柄。

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

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

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

通道创建步骤涉及使用 configtxgen 在本章早些时候创建的创世块来初始化区块链。创世块只是追加到链上的第一个配置块。配置块包含对通道的规范以及其中所包含的组织等内容;这样的块不包含链码交易。在第九章区块链网络中的生活中,我们将再次处理配置块,讨论如何扩展网络。

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

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

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

加入通道

现在 tradechannel 已经创建,我们的四个对等方,每个组织一个,必须加入通道,这一步骤在每个节点上初始化分类帐,并准备对等方在其上运行链码和交易。为此,我们需要重用在前一步骤中创建的客户端句柄,或者使用类似操作序列实例化一个。此外,我们必须实例化一个通道句柄,注册订购方,并获取创世块(在使用通道配置在创建步骤中隐式发送到订购方),如 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 函数在提交通道加入请求之前将特定组织的管理员的签名标识与客户端对象关联起来。此请求包含创世块以及该组织中每个对等方的配置(从 config.json 中每个组织中包含 peer 前缀的属性中加载,如上面的代码所示)。

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

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

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

链码的安装

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

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 内部,安装请求将链码的源代码打包到一种称为ChaincodeDeploymentSpec(CDS)的规定格式中(github.com/hyperledger/fabric/blob/release-1.1/protos/peer/chaincode.proto)。然后对该包进行签名(由与客户端对象关联的组织管理员签名),以创建SignedChaincodeDeploymentSpecgithub.com/hyperledger/fabric/blob/release-1.1/protos/peer/signed_cc_dep_spec.proto),然后将其发送到生命周期系统链码(LSCC)进行安装。

上述过程描述了每个 Signed CDS 实例仅具有签发安装请求的客户端相关联的标识的签名的简单情况。Fabric 支持更复杂的场景,其中 CDS 可以(在带外)传递给不同组织的不同客户端,并在收到安装请求之前由每个客户端签名。鼓励读者使用可用的 API 函数和 Fabric 数据结构尝试此变体(hyperledger-fabric.readthedocs.io/en/latest/chaincode4noah.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,其中确实包含了我们在第四章中开发的源代码,使用 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实现。此函数既可用于实例化新部署的链码,也可用于更新已在通道上运行的链码(见第九章在区块链网络中的生活)与之前的阶段一样,我们必须创建客户端和通道句柄,并将通道句柄与客户端关联。此外,必须将网络中的所有背书对等方添加到通道中,然后必须使用与通道关联的通道对象来初始化通道的与通道相关联的 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 和版本必须与安装提案中提供的内容匹配。此外,我们必须提供将发送到链码并执行的函数名和参数列表(在我们的链码中,这将执行 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.jsinvokeChaincode函数中。

调用chaincode的过程与我们进行实例化时相同,代码也类似。调用者必须构建一个包含要调用的chaincode函数的事务提议,以及要传递给它的参数。仅提供chaincodeID(在我们的实现中为tradecc)就足以识别chaincode进程以引导请求:

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

与实例化提议的一个区别是,这个操作通常不需要组织中的管理用户;任何普通成员可能就足够了。这个提议必须发送给足够的背书对等体以收集满足我们的背书策略的正确签名集。这是通过将我们网络中的所有四个对等体添加到频道对象中来完成的(必须与之前的阶段一样创建和初始化频道对象)。一旦提议响应被收集并得到验证,与实例化提议一样的方式,必须构建一个交易请求并发送给订购方:

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

我们在createTradeApp.js中的测试脚本中调用invokeChaincode。我们希望执行的链码函数是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’)和组织:这里我们希望出口商组织的成员检查交易请求的状态。

由于查询是对客户端及其相关节点的本地查询,因此响应会立即返回给客户端,无需订阅(如调用的情况)。

完成循环 - 订阅区块链事件

正如我们在前几章中所见,权限区块链上的共享账本承诺需要网络节点之间的共识。Hyperledger Fabric 在其 v1 版本中有一个更独特的过程来提交到账本:交易执行、排序和提交过程都是彼此解耦的,并被构建为管道中的阶段,在其中背书者、排序者和提交者独立执行其任务。因此,在 Fabric 方案中,导致区块提交到账本的任何操作都是异步的。我们在中间件中实现的三个操作都属于这一类别:

  • 通道加入

  • 链码实例化

  • 链码调用

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

Fabric 在 SDK 中通过 EventHub 类提供了一个事件订阅机制,相关的订阅方法分别是 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)与从区块中提取的通道名称进行匹配。(区块有效载荷是使用 Fabric 源代码中的标准模式构造的,在protos文件夹中可用。理解和玩弄这些格式留给读者作为练习。)我们将在代码中设置超时时间(这里为 40 秒),以防止我们的事件订阅逻辑无限等待并阻塞应用程序。最后,通道加入的结果不仅取决于channel.joinChannel调用的成功,还取决于区块事件的可用性,如下所示:

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

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

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();
    }
  }
);

传递给回调的参数包括事务句柄和状态码,可以检查链码调用结果是否成功提交到分类帐。一旦收到事件,事件监听器就会被注销以释放系统资源(我们的代码也可能会监听区块事件而不是特定的事务事件,但那样就必须解析区块有效载荷并查找和解释提交的事务的信息)。

将所有内容整合在一起

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

node createTradeApp.js

作为一个练习,也是为了测试中间件库函数和链码,您可以通过开始接受出口商的贸易请求,并最终由进口商向出口商支付完整的货物运输费用来完成createTradeApp.js脚本开始的贸易场景。要查看这个操作,请运行以下命令:

node runTradeScenarioApp.js

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

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

应用程序

参考图 5.2:Hyperledger Fabric 应用程序的典型三层架构,以及本章的 应用模型和架构 部分的讨论,Hyperledger Fabric 应用程序的不同用户可能需要不同且独特的应用程序。我们的交易场景就是一个例子:代表交易方、银行、承运人和政府部门的用户可能需要我们的应用程序中的不同内容,即使他们共同参与交易网络并认可智能合约操作。

不同组织的管理员必须具备执行的共同操作。这包括从创建通道到实例化链码的各个阶段。因此,如果我们需要为每个网络参与者构建不同的应用程序,我们应该向这些应用程序的每个实例提供这些功能。一旦我们进入应用程序本身,它由链码提供的一组调用和查询功能组成,我们必须为区分留出空间。为交易方及其银行设计的应用程序必须向用户公开交易和信用证操作。然而,并不需要将这些操作暴露给承运人,因此,为后者设计的应用程序可以并且应该限制提供给其的功能,仅限于影响承运人角色的功能,例如创建提单和记录货物位置的功能。

在这里,为了简单起见,我们将所有应用程序汇集成一个,并演示如何使其工作。基于用户角色和要求的多样化留给读者作为一项练习。我们的汇编应用程序将作为一个 Web 服务器实现,松散地连接智能合约和中间件,从最终用户那里发出声音。

用户和会话管理

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

应用程序必须具有身份验证层以及会话管理机制,允许已经通过身份验证的用户行使应用程序,其权限受其角色的限制。 在我们的示例应用程序中,我们将使用JSON Web TokensJWT)来实现这一目的。

设计 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(expressjs.com/)web 应用程序,以暴露前述 API。 代码位于我们仓库的应用程序文件夹中,源代码在 app.js 中,依赖项在 package.json 中定义。 作为运行 web 应用程序的先决条件,必须安装依赖项,可以通过运行 npm installmake(参见Chapter 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 服务器,以与客户端进行安全、机密的通信。

现在,让我们看看如何配置各种 express 路由来实现我们的服务 API 功能。

用户和会话管理

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

  1. 为用户创建一个具有 60 秒过期时间的 JWT 令牌

  2. 注册或登录用户

  3. 如果成功,将令牌返回给客户端

服务器期望提供请求体中的表单数据,以提供注册或登录的用户名和组织名。 管理用户仅由管理员用户名标识。 请求体格式为:

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

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

这是一个天真的实现,但是由于 Web 应用安全不是本教程的重点,这足以展示如何在智能合约和中间件上实现服务器和前端。

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

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

读者可以尝试使用会话管理的其他机制,例如会话 cookie,而不是 JWT 令牌。

我们的 Web 应用现在可以进行测试了。首先,使用 docker-compose(或 trade.sh)启动 Fabric 网络,就像本章前面所示的那样。

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

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

node app.js

在第三个终端窗口中,使用 curl 命令向 Web 服务器发送请求,以创建名为 Jim 的普通用户在 importerorg 组织中(这是在 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'

你应该看到以下输出(管理员用户已经注册,所以这实际上是一个登录调用):

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

在中间件中,在这里执行的函数是 clientUtils.js 中的 getMember

网络管理

正如你在 app.js 中所看到的,从通道创建到链码实例化的 API 函数都被实现为 express 路由:

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 令牌值>。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"] }'

现在,输出应包含链码响应:

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

用户/客户端交互模式

虽然运行 curl 命令足以测试我们的 Web 应用程序,但向用户公开应用程序的适当方式是通过一个或多个网页,其中包含用户触发这些命令的小部件。

正如我们在中间件实现部分看到的,包括链码调用在内的各种操作都是异步的。在我们的实现中,我们通过使包装函数返回给调用者来遮蔽了这种异步行为,但仅当请求已成功发送到订购者并且已收到并验证了已订阅的事件时才返回。我们还可以选择将此异步行为暴露给 Web 应用程序客户端。使用 Web Sockets (developer.mozilla.org/en-US/docs/Web/API/WebSockets_API),当事件通知到达与事件中心注册的回调时,向最终用户呈现的 Web 界面内容可以动态更新。

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

测试中间件和应用程序

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

与现有系统和流程的集成

在与客户讨论端到端解决方案时,我们经常解释说,与整体足迹相比,与区块链相关的组件所占比例很小。这仍然是一组非常重要的组件,但它们代表的足迹很小。

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

我们将探讨我们已经利用的各种集成模式,并查看一些非功能性要求如何影响集成部署。最后,我们将探讨一些在设计集成层时集成者需要牢记的其他考虑因素。

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

  • 了解集成层的设计考虑

  • 查看集成设计模式

  • 探索非功能性要求对集成的影响

设计考虑

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

根据设计考虑,我们将考虑以下几个方面:

  • 去中心化的影响

  • 流程调整

  • 消息关联

  • 服务发现

  • 身份映射

去中心化

曾经有很多尝试标准化 IT 功能和能力,但现实是没有两个组织拥有相同的 IT 景观。即使对于那些选择了相同的 ERP 供应商的组织来说,系统也会被定制以满足组织的流程和需求。

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

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

尽管对一些人来说可能令人惊讶,但请记住,一个网络可以是财富 500 强组织和初创企业的混合体。想象一下供应链行业;您会发现一些几乎没有 IT 基础设施的卡车公司,一直到行业巨头。显然,一刀切可能不适用于所有情况。

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

  • 网络提供集成资产:这可以采用每个参与者在其自己的基础设施中部署的网关形式。网关对每个人都是标准的,并以一致的方式管理智能合约的调用。

    这可以提供加速入职流程的好处,但需要考虑谁拥有、管理和支持这个 IT 组件。此外,由于信任问题,一些组织可能不希望部署此基础设施。

  • 每个参与者构建自己的集成层:这种方法的明显缺点是所有参与者都要重新发明轮子,但它减少了通过在每个组织中部署一个公共组件所创建的潜在支持问题。

    对于需要深度系统集成以实现流程优化效益的用例,这也可能是首选方法。

流程对齐

集成层将不得不处理两种不同的观点:

  • 组织 IT 系统和业务流程视角:组织业务流程可能托管在诸如 SAP 等 ERP 中。在这种情况下,当特定的业务事件需要调用智能合约时,可能会通过 SAP 系统的 业务 APIBAPI)调用来发出。来自 ERP 的 API 调用可能包含各种数据结构,其中一些与区块链网络完全无关。

  • 智能合约视角:这个视角具有数据表示是应用程序不可知的特点。这意味着网络的所有参与者都将理解正在处理的数据的性质。

它取决于集成层来协调两者,并确保在两个系统中保持正确的交易语义。这可能意味着:

  • 映射:将数据从一个字段名移动到另一个字段名

  • 转换:基于输入聚合、拆分或计算新值

  • 交叉引用:利用参考表将应用程序特定代码映射到网络识别的值

这里的观点是,即使你的网络同意使用第七章中介绍的 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网关时,可以配置它来支持多个用户。服务器利用节点密码框架来管理此身份验证。这提供了支持不同模型(例如用户 ID/密码,JWT 和 OAUTH)的灵活性。

客户端一经验证通过服务器,还有一个额外的步骤,即将 Hyperledger Composer 商业卡加载到服务器的用户存储库中。客户端与服务器之间需要有隐式信任,因为商业卡包含私钥。

集成设计模式

现在我们将看一些我们在行业中见过的可行集成模式。这个列表并不是详尽无遗的,考虑到我们仍处于 Hyperledger Fabric 和 Composer 解决方案的早期阶段,我们预计随着人们和组织对技术变得更加熟悉,将会出现新的模式。

企业系统集成

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

与现有记录系统集成

以下是一张图来说明将区块链网络集成到现有记录系统中的过程:

图 5.6:将区块链网络集成到现有记录系统中

大多数寻求加入业务网络的大型企业最终将目标定在集成其记录系统,以确保他们从实时透明的交易分配中受益。在这种情况下,我们之前提到的流程对齐将非常重要。

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

这里需要注意的重要事项是,这种类型的集成将针对一个组织,很少有可能重用。

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

  • REST 网关:暴露与 Fabric 智能合约对齐的 REST 接口

  • 集成总线:映射字段并连接企业系统

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

与运营数据存储集成

这里有一张图,展示了将区块链网络集成到运营数据存储中的过程:

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

通常,组织正在寻找方法对其分类帐中的信息进行分析。然而,对组织的对等方发出多个/大型查询将仅影响系统的在线性能。通常,在企业系统设计中公认的方法是将数据移动到运行数据存储中。然后可以轻松查询数据。可以通过使用不同的数据来源使数据进行丰富处理,从而创建数据的附加视图。

在这种模式下,事件监听器订阅了 Fabric 组织的事件。因此,它可以接收组织有权访问的所有通道的交易。如果数据的完整性保护很重要,事件监听器可以计算每条记录的哈希值,并将其与记录一起存储。

你会注意到该模式还考虑了一个名为syncAll的函数,它将允许事件监听器与世界状态的最新视图重新同步数据存储。请记住,这个syncAll函数的实现需要谨慎进行,并且很可能需要支持结果集的分页。

微服务和事件驱动架构

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

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

我们将此模式标记为微服务和事件驱动,因为这是这些类型架构中最常见的模式。然而,该模式的特殊之处来自于网关。这样的系统不会执行任何数据映射;它将利用共同的通信协议(HTTP)和数据格式(通常为 JSON,但也可以是 XML)。此外,服务已经设计为理解所进行交易数据的语义也是一个期望。事件也通过相同的协议和数据格式传播。

再次强调,微服务应用往往是较新的应用程序,并且会从更细粒度的接口受益。因此,它们 tend to evolve more quickly and be in a position to adapt and adhere to the transactions from the network. Similarly, event-driven applications will benefit from their low coupling to the other components of the system, and so are good candidates for this pattern.

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

软件或硬件组件的故障对于任何工业应用来说都是不可避免的事实,因此您必须设计您的应用程序能够抵御故障并最大程度地减少停机的可能性。我们将讨论业界广泛使用的三个关键指导方针,以构建和维护系统,并简要审视它们如何适用于使用 Fabric 或 Composer 工具构建的应用程序。

可靠性

可靠的系统是在面对故障时确保正确运行的系统,概率很高。这涉及以下几点:

  • 系统的持续自我监控

  • 组件中故障或损坏的检测

  • 修复问题和/或切换到正常工作的组件

虽然行业中已经发展出了各种实践以确保可靠性,但冗余和故障转移通常(甚至普遍)被使用。

在我们在第一部分构建的 Fabric 应用程序的背景下,这具有某些含义。请回想一下,Fabric 有许多不同的组件,它们必须协同工作(虽然是以松散耦合的方式)以确保成功运行。排序服务是其中一个关键组件,如果它失败了,将会完全停滞事务流水线。因此,当构建生产版本的比如说我们的交易应用程序时,您必须确保排序器具有足够的冗余性。在实践中,如果您的排序器是一个 Kafka 集群,这意味着必须确保有足够的 Kafka 节点(broker)来接管应付一个或多个失败的情况。

同样,对于背书和承诺的对等体的可靠性对于确保交易完整性至关重要。虽然区块链作为共享复制账本被设计为在对等体故障时具有一定的健壮性,但其易受攻击的程度可能因应用程序而异。如果一个背书对等体失败,并且其签名是满足交易背书政策所必需的,那么就无法创建交易请求。如果一个背书对等体行为不端,并且产生了错误的执行结果,那么交易将无法提交。在任何一种情况下,系统的吞吐量都会降低或降为零。为了防止这种情况发生,您应确保在每个组织内建有足够的冗余,特别是对于满足背书政策的关键对等体。以下图示了一种可能的机制,通过该机制,交易提案将被提供给多个对等体,并且使用多数规则丢弃缺席或错误的响应:

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

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

可用性

可用性标准与可靠性密切相关,但更多关乎确保系统的高概率运行时间,或作为推论,最小化系统停机的概率。与可靠性一样,检测故障节点并确保充足的故障转移是确保您的应用仍将正常运行的关键。确定所需的可用性水平,分配足够数量的冗余和自我纠正组件资源,并在生产环境中进行测试,是确保您的应用能够获得期望性能的必要条件。

可维护性

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

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

在具有松散耦合组件的区块链应用中,建议为每个组件(订购者、节点和 MSP)都提供蓝色和绿色环境,并分阶段进行升级和测试,或者一次只升级一个组件集群,以减小出错的机会。

摘要

构建一个完整的区块链应用是一个雄心勃勃且具有挑战性的项目,不仅因为它需要各种技能——系统、网络、安全和 web 应用开发等,而且因为它需要跨越多个安全域的多个组织共同进行开发、测试和部署。

在本章中,我们从一个简单的智能合约开始,并以一个准备好驱动贸易场景并在一个防篡改、共享、复制账本中存储记录的四节点区块链网络结束。在此过程中,我们学会了如何设计组织结构并配置 Fabric 网络。我们学会了如何构建一个通道,或者说一个 Fabric 区块链的实例,让网络中的节点加入该通道,并在该通道上安装和实例化一个智能合约,使用 Fabric SDK。我们学会了如何通过 Web 应用程序向最终用户公开我们网络和智能合约的功能,公开服务 API。我们还学会了超级账本 Fabric 交易流水线的工作原理,以及区块提交操作的异步性质如何影响端到端应用程序的实现。

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

超级账本平台和工具毫无疑问会随着时间的推移而发展,以满足行业和开发者的需求,但我们在应用构建过程中描述的架构和方法论,以及设计和集成模式,应该会长期作为教育指南。

我们迄今为止的旅程已经带领我们到了超级账本 Fabric 框架的基础。我们已经使用链码并使用 Fabric SDK API 集成了一个应用程序。这些都是必不可少的技能。

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

第六章:商业网络

本章介绍并探讨了一个新概念——商业网络。通过理解什么是商业网络以及它们如何运作,您将能够理解区块链技术如何彻底改善它们。特别是,区块链,尤其是超级账本 Fabric 区块链,对商业网络提供了重大好处,因为它彻底简化了将企业联系在一起的信息和流程,既降低了成本,又为网络内的企业创造了新机遇。

我们将看到商业网络的概念允许您通过查看其与之交互的交易对手来分析企业。虽然商业网络是特定于行业的,但单个网络可以用于支持多个用例,并链接到其他商业网络以形成网络之间的网络。

我们将花一些时间介绍商业网络的词汇,介绍关键术语如参与者资产交易事件。然后将这些元素组合起来来定义正在分析的商业问题的行为。我们能够使用业务需求来创建一个技术蓝图,该蓝图可用于实施解决方案。通过本章的学习,您将准备好使用超级账本 Fabric 和 Hyperledger Composer 来实施这些想法,而您将在接下来的章节中做到这一点。

尽管在实施区块链网络之前理解商业网络的概念是必要的,但您会发现它对更广泛的问题很有帮助,例如执行区块链分析、与现有系统集成以及如何构建应用程序和企业架构。从这个意义上说,本章可以单独阅读,而不必在之后实施网络。

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

  • 商业网络的语言

  • 商业网络的概念

  • 定义商业网络

  • 介绍参与者

  • 介绍资产

  • 介绍交易

  • 介绍事件

  • 实施商业网络

一个充满有意义的活动的繁忙世界

想象一下我们正在一架飞机上飞越一个大城市。我们可以看到工厂、银行、学校、医院、零售店、汽车展厅、港口的船只等等。这些是定义城市的结构。

如果我们仔细观察,就会看到在这些结构内部和之间发生的事情。卡车可能正在向工厂运送铁矿石,客户可能正在从银行提取资金,学生可能正在参加考试——这是一个繁忙的世界!

而且,如果我们能再仔细一点看,我们会看到所有这些人和组织都在与彼此进行有意义的活动。学生从老师那里接受评估,随后这将帮助他们进入大学。银行向可以搬家的客户提供贷款。工厂从原材料中制造组件,然后客户将其组装成复杂的物体。人们从经销商那里购买二手车,他们用来每天上班或度假!

我们可能会对所有这些结构和它们之间的过程的多样性感到惊叹。我们甚至可能会想知道它是如何如此轻松地共同运作的!

然后我们可能会反思所有这些多样化的活动,并想知道它们是否都有共同之处?是否有可重复的模式可以让我们理解所有这些复杂性?是否有一种分辨率,在这种活动看起来都一样?所有这些人和组织,在某种意义上,是在做同样的事情吗?

答案当然是肯定的!接下来的一节将会给我们一个更好的解释。

为什么要有一种用于商业网络的语言?

商业网络是一种思维方式,它允许我们审视所有这些活动,并用非常简单的语言描述它。而且,由于我们试图用一种对区块链有意义的语言来描述世界,而区块链是一种简单的技术,我们期望那种语言的词汇是简单的。在下一节中,你将会看到它就是这样!

但在我们深入探讨之前,让我们问一下为什么我们想要创造一种区块链能够理解的语言?嗯,如果我们可以的话,我们就可以将区块链的所有好处带到用那种语言描述的世界。而且,我们可以简洁地总结这些好处——增加的信任。

增加的信任意味着学生可以向他们的大学展示他们的高中证书,大学可以对资格的真实性感到放心。它意味着银行可以以最低的利率向客户提供贷款,因为它可以对客户的财务状况感到放心。它意味着组件制造商可以为他们的产品收取更高的价格,因为他们的客户可以确信原材料的质量,知道它们的来源。最后,购买二手车的买家可以对他们的购买感到放心,因为他们可以证明它以前只有一个细心的车主!

定义商业网络

我们可以用商业网络的概念总结所有这些想法:

商业网络是一组参与者和资产,经历由交易描述的生命周期。当交易完成时,事件发生。

你可能会想知道这意味着什么。经过所有这些铺垫之后,我们告诉你几个看似简单的句子描述了所有这些复杂性?

简单的答案是肯定的——我们很快会通过更详细地描述参与者资产交易事件来解释。然后,你会发现所有这些丰富的行为都可以用相对简单的语言词汇来描述。

更深层次的想法

实际上,在业务网络背后有一个更深层次的想法——即技术的语言和词汇应与业务领域的语言和词汇紧密匹配,消除业务概念和技术概念之间的重大翻译需求。业务网络通过用与业务相同的语言描述基础技术,摆脱了断开连接的技术概念。这使得更容易思考世界,并更准确地将思想转化为一个完全运作的系统。

在实践上,这意味着虽然我们对业务网络的初始词汇很简单,但它是一个可以随着时间逐渐丰富的语言的开端,只要它描述了发生在现实世界中的细节和细微差别。我们以后会回到这个想法,但现在让我们从了解参与者开始。

介绍参与者

威廉·莎士比亚说,世界是一个舞台,在这个舞台上男人和女人是演员。以类似的方式,业务网络有一个演员阵容——一组相互交互以获得某种形式互惠的演员。我们称这些演员为业务网络中的参与者。例如,教育网络中的参与者可能是学生、教师、学校、大学、考官或政府检查员。保险网络中的参与者可能是投保人、经纪人、承保人、保险公司、保险联合体、监管机构或银行。

参与者的概念对于理解业务网络至关重要。起初,你可能会觉得这个术语有点令人生畏,但实际上没有什么可担心的。理解的关键在于名字——参与者参与了业务网络。我们感兴趣的是他们的行动。用不同形式的词来强调他们相互作用的不同方面:参与者、当事人和交易对手,例如。所有这些形式都源自行动的概念。和往常一样,我们发现诗人对世界运作的方式了解一二!

学会喜欢这个词,因为它是一个开启大门的词!这是你理解业务的创立原则的简称——与谁做生意至关重要。但它比这更重要;确定业务网络中的参与者是确定是否有机会从区块链的使用中获益的第一件事情。你需要先了解人员构成,才能真正了解发生了什么。而且,随着你了解参与者之间的互动,你将能够提高对成为特定参与者意味着什么的理解。

参与者类型

商业网络中有不同类型的参与者,我们将它们分为三大类。令人惊讶的是,我们不会首先描述最重要的类别!

个人参与者

希望这个类别是一个相当明显的类别——教师、学生或银行客户都是个人参与者的例子。无论你把它们称为个体、人员,甚至是人类,这个第一个类别是我们直觉上会认为是参与者的,因为我们将它们与自己联系起来。

你可能认为个人是网络中最重要的参与者。毕竟,企业的存在是为了服务个人,不是吗?是的,他们是,但情况比较微妙。虽然商业网络通常存在是为了满足个体最终消费者的需求,但区块链是一种对于网络中的企业更有价值的技术。这是因为它使它们能够更好地协调彼此的活动,从而降低成本,并为最终消费者提供新的商品和服务的机会。这就是为什么你会听到人们说出类似区块链对于 B2B 比 B2C 或 C2C 更重要的句子——他们试图传达的是商业网络的重大成功是将区块链用作高效和创新的企业间交互的普遍基础。

当然,个人参与者也很重要。企业需要了解他们的最终消费者,而最终消费者通常使用商业网络提供的服务与彼此互动。例如,如果我希望通过银行网络向你转账,我们各自的银行就需要知道我们是谁,以便能够正确验证和路由交易。

最后,一个合理的经验法则是,商业网络中认识的个人要比网络中的企业多。这并不令人太惊讶——只是值得指出这一点,以便你对什么是个人参与者的理解是完整的!

组织参与者

组织参与者是商业网络中最重要的角色。汽车经销商、银行、学校和保险公司都是组织参与者的例子。当我们首次思考特定的商业网络时,我们会识别这些参与者,然后是它们互相以及最终消费者提供的商品和服务。这些组织参与者为商业网络提供基础设施——使其运作的人员、流程和技术。

虽然组织由个体构成,但在概念上与它们是完全分开的。一个组织有着自己的身份和目的。它以非常实际的方式存在,独立于属于它的个体。组织为商业网络提供了一种永恒感。虽然组织内的个体可能随着时间变化,组织内的个体数量可能增加或减少,甚至组织内的不同角色可能来来去去,但组织保持不变;它是一个比任何个人成员的寿命都要长得多的结构。

关于个体与他们的组织之间关系性质的最后一点需要注意的是,是个体执行组织的功能,如个体的组织角色所定义的。当银行向客户提供贷款时,是银行员工代表银行执行的。通过这种方式,个体是组织的代理人,并且个体的角色决定了它可以执行的任务集。例如,学校教师可以给学生布置家庭作业,但需要学校校长来聘请新教师。简而言之,个体代表组织行事,并且代表该组织的权威。

系统或设备参与者

系统或设备参与者代表商业网络中的技术组件。实际上,它们是一种特殊类型的个体参与者,如果你觉得有用,可以这样认为。然而,我们之所以单独将它们列出来,有两个原因。

首先,如今的商业网络中有很多技术组件!例如,有 ERP 系统、支付引擎、预订系统、交易处理器等等。事实上,今天商业网络中的大部分重活都是由这些系统完成的。这些系统与拥有它们的组织相关联,就像我们之前讨论过的个体一样,这些系统代表它们的拥有组织行事——它们也是其代理人。

将区块链纳入商业网络将增加更多的系统参与者,其他参与者(个体、组织和系统/设备)可以与之互动。了解这些区块链系统参与者是很重要的,因为它们将为商业网络提供非常有用的服务!

其次,设备正变得成为业务世界中更为重要的一部分。而且,尽管今天许多设备相对简单,毫无疑问,设备正在获得更多自主特性。我们都听说过预期出现自动驾驶车辆,正是出于这种精神,我们引入设备参与者的概念。逐渐重要的可能是将这些设备视为业务网络中更为积极的角色。因此,虽然我们不指望汽车很快就变得智能(不管那意味着什么!),但将这些越来越自主的设备称为网络中的活跃实体而不是被动实体是有帮助的。

参与者是代理

我们对参与者类型的考察告诉我们一个共同点——它们都拥有一定程度的主动性——它们积极地做事情。虽然系统和设备的自主程度受其程序和算法的限制,但将它们这样地看待仍然是有帮助的。而且,这些相对自主的参与者之间的交互作用成为业务网络中下一个概念的引导,即资产。我们稍后会看到,在参与者之间移动的实体——资产——没有这种自主性。这些都受到参与者施加在它们身上的力量的影响。稍后再详述此点。

参与者和身份

最后,但非常重要的是,参与者有身份。例如,一个学生有学生证,一个司机有驾驶执照,一个公民有社会保障号码。显而易见,参与者和用于识别参与者的东西之间有区别。而且,将这两个概念紧密联系但分开是非常重要的。

例如,参与者可能有不同的身份参与不同的业务网络——可能是同样的银行参与了保险网络和抵押网络,但它在这两个网络中有不同的身份。此外,即使在一个单一网络中,参与者的当前身份可能会被 compromise,允许他们被冒充。在这种情况下,他们被 compromise 的身份将被吊销,并发放一个替代身份供真正的参与者使用,拒绝冒名顶替者,恢复信任。不同身份,但是同一参与者——这就是要传达的信息。

正是因为对冒充的担忧,某些身份会定期被故意过期。例如,X.509 数字证书有一个到期日期,过了这个日期,它们将不再有效。然而,仅仅因为证书已经过期,并不意味着参与者不再存在。

实际上,情况恰恰相反。参与者相对于它的身份的相对永久性意味着它可以用来提供业务网络中谁做了什么的长期历史参考。参与者随时间提供的身份的一致性帮助我们推理业务网络中的交互历史。我们可以不使用参与者的概念就做到这一点——只使用身份,并清楚地了解它们如何以及何时相互之间改变,但这将不那么直观。

关于参与者的内容就到这儿了;你现在是专家了!正如你所看到的,参与者可能是业务网络中最重要的事情,这就是为什么我们花了相当多的时间讨论它们。现在让我们把注意力转向在参与者之间流动的对象,即资产。

介绍资产

我们已经看到业务网络是由在其中运作的参与者定义的。这些参与者是在网络中进行有意义交互的积极主体,他们的交易至关重要。现在我们要问自己一个问题,*参与者之间流动的是什么?*答案很简单,是资产。

要理解我们所说的资产是什么,让我们看一些例子。我们注意到,学生从导师那里接收课程作业。同一个学生随后可能向大学展示他们的教育证书。汽车经销商向买家出售一辆汽车。保险公司为投保人提供汽车保险,签发一份保单。投保人提出索赔。这些例子都包含了资产:课程作业、教育证书、汽车、保单和索赔。

资产在参与者之间流动

我们可以看到,资产是在参与者之间流动的对象。而参与者有相当大的自治度,资产则相当被动。这种资产的性质是基础的——资产往往对于交换它们的交易对手来说意义重大。这并不是说其他参与者对这些资产不感兴趣,但这确实强调了资产的被动性质。那么,是什么让资产如此重要呢?我们为什么要讨论这些被动的对象呢?

答案就在我们选择的词——资产。资产是一个有价值的东西。尽管资产相对被动,但它们代表着在参与者之间交换的价值。再用这种基于价值的视角看一下这些示例资产:课程作业、教育证书、汽车、保单和索赔。课程作业对老师和学生有价值;教育证书对学生和大学有价值;汽车对经销商和买家有价值;保单对保险公司和投保人有价值;索赔对申请人和保险公司有价值。希望现在清楚了为什么资产很重要,以及为什么它们被称为资产!

作为一个小注,不要认为因为我们有资产,我们就必须有负债——我们并不是完全按照这种方式使用这个术语。完全正确的是,如果我们要将对象作为为我们服务或反对我们的对象进行计量,我们将把它们称为资产或负债,但这不完全是这里发生的情况——我们使用资产作为具体名词,而不是作为质量或抽象名词。

有形资产和无形资产

让我们通过考虑有形和无形资产来继续了解资产。有形资产是我们可以触摸和感受的东西——汽车、纸币或课程作业。无形资产是诸如抵押贷款、知识产权、保险单和音乐文件等东西。在一个日益数字化的世界中,我们将会看到更多的无形资产。你会听到人们说物体正在变得非物质化,而无形资产的概念很好地捕捉了这个概念。

几个小点需要注意,以避免混淆我们对无形一词的使用。首先,由于我们正在处理数字分类帐,在某种微不足道的意义上,区块链上的一切都是无形的。有趣的是对象本身的性质——使用无形这个词可以帮助你记住要注意在物理世界中看不见的东西。

其次,使用无形一词并不意味着对价值的陈述。在会计系统中,当我们难以定义某物时,例如商誉,我们经常使用这个术语。同样,我们并不是用这个词的这个意义;我们的无形资产比这更具有具体、明确和可交换的形式,因为它们是有价值的东西,即使你不能触摸它们。

资产的结构

现在让我们重新聚焦,看看资产的结构。资产具有一组称为属性的属性集和一组称为关系的属性集。属性属性易于理解——它们是对象的特征。例如,汽车有制造日期、颜色和发动机尺寸。或者,抵押贷款有价值、寿命和还款计划。特定资产由特定的一组属性标识。例如,我的汽车可能是 2015 年生产的,是白色的,有 1.8 升的发动机。另一个例子——你的抵押贷款可能价值 10 万美元,寿命 25 年,并且每月付款。区分这种差异很重要——在资产的结构一般类型和资产的特定 实例之间。

第二,资产还有一组被称为关系的属性。关系是一种特殊类型的属性—它是对另一个资产的引用!你可以立即看出这为什么很重要。例如,一辆汽车有一个保险文件。汽车是一个有价值的对象,保险文件也是一个有价值的对象。此外,保险文件命名了一个保单持有人。在我们的例子中,主体和客体都是资产,并且它们以提供基本含义的方式相互关联。

后面我们会看到,描述或建模这些关系是一项极其重要的活动,因为我们在描述世界是如何运作的。在前面的例子中,我们故意犯了一个错误—是的,真的!这是因为在现实世界中,实际上是政策文件是核心,因为它命名了汽车和保单持有人。在建模中,我们称之为关联关系,并且我们将看到为什么正确理解这种事情非常重要。例如,在汽车的性质中,你不会找到保险文件—汽车是在有效的保单文件中命名而被保险的。此外,如果我想要让更多的人驾驶汽车,我会将他们的名字添加到保单文件中,而不是汽车!关于这一点,以后会有更多的讨论—现在,记住资产具有属性和引用,特定对象对这些属性具有具体的值就足够了。

也值得简要提及使资产属性成为属性而不是对另一个资产的引用的性质。一个简单的答案是:当属性变得太时,将它们拆分成资产引用!当然,这是一个非常不满意的答案!为什么呢?因为我没有告诉你什么定义了大!一个更好的答案是,当属性满足单独的关注时,需要引用。这个原则—关注分离—是任何系统中的关键设计原则。例如,对于保险政策,政策的有效日期不是一个单独的关注点,但汽车和命名的驾驶员是单独的关注点。这个原则帮助我们独立地思考保险政策、汽车和驾驶员,从而更真实地建模现实世界。最后,在资产的这个方面,属性和关系属性是领域特定的—它们与手头问题的性质有关。所以,对于汽车制造商,颜色可能是汽车的属性—但对于油漆制造商,颜色绝对是一种资产类型!

所有权是一种特殊的关系。

有一种特定的关系在商业网络中尤为重要,那就是所有权的概念。所有权是一种关联关系,例如我们之前讨论过的保险单文件。让我们考虑一个具体的例子——一个人拥有一辆车。车主是车的属性吗?车是人的属性吗?经过一点思考,我们可能意识到,这两种说法都没有捕捉到拥有某物的含义。所有权是人和车之间的映射。所有权是一个与车和车主完全不同的概念。

了解这种关于所有权的思考方式很重要,因为在许多情况下,我们通过车或车主来建模所有权关系,而这对许多目的来说已经足够了。但是,所有权关系的本质是一种关联关系,意识到这一点很重要——因为区块链经常用于记录商业网络中的所有权和所有权转移。例如,政府经常持有土地或车辆的所有权记录。在这些情况下,考虑的主要资产是所有权关系。当车辆或土地在参与者之间转移时,改变的是这些所有权记录,而不是资产本身。这很重要,因为我们经常对车辆或土地的历史感兴趣,而车辆或土地本身可能不会改变,但它的所有权肯定会改变。因此,清楚地知道我们是在谈论资产的历史,还是在谈论所有权的历史很重要。这些历史通常被称为溯源——它们告诉我们谁拥有了一个资产,以及它如何随着时间的推移而改变。这两个方面都很重要,因为了解资产的溯源可以增加我们对其的信心。

资产生命周期

这种出处的想法自然地将我们带到了资产生命周期的概念。如果我们考虑一下资产的历史,那么从某种非常有意义的意义上讲,资产是被创建的,随着时间的推移而发生变化,并最终停止存在的。例如,考虑一笔抵押贷款。当银行同意向客户借出一笔钱时,它就产生了。它在抵押贷款的期限内存在。随着利率的变化,它根据固定利率或浮动利率确定每月偿还金额。抵押贷款的期限可以在银行和抵押贷款持有人的同意下更改。最后,在抵押贷款到期时,它停止存在,尽管可能会保留其历史记录。如果客户希望提前支付抵押贷款(也许他们搬家了),或者不幸地,如果他们违约了,抵押贷款可能会被提前终止。在某种意义上,我们看到抵押贷款被创建了,期限定期更改了,然后抵押贷款以正常或意外的方式完成了。这种生命周期的概念在业务网络中非常重要,我们将在后面详细讨论它,当我们讨论交易时。

回到资产,我们可以看到在它们的生命周期中,资产也可以被转化。这是一个非常重要的想法,我们考虑资产转化的两个方面——即转化是否涉及分割聚合,以及它是否是均匀不均匀的转化。这些术语听起来有点吓人,但它们非常容易理解,并且最好使用每个方面的一个示例来描述。

在第一个例子中,我们考虑了一颗被开采出来的珍贵宝石。一般来说,一颗开采出来的宝石对于任何珠宝商来说都太大了,无法用于制作单件珠宝。它必须被分割成更小的石头,每个石头可以用于制作单件珠宝。如果我们看一下一颗大的被开采出来的宝石的历史,我们会发现它经历了一个分割的过程。最初的资产是一颗宝石,它被转化为一组更小的宝石,每个宝石都与原始宝石相关。我们可以看到资产转化是均匀的,因为尽管更小的宝石绝对是不同的资产,但它们与原始资产是相同类型的。类似的均匀转化过程经常发生在无形资产中,例如,当一笔大型商业贷款或保险请求被分散到几家公司中以分散风险时,或者当股票被拆分时。

在我们的下一个例子中,我们考虑使用较小的宝石的珠宝商。我们想象他们使用宝石为客户制作精美的戒指。为了制作戒指,他们运用所有的技能将宝石镶嵌在一个通过肩连接到环上的镶座上。珠宝商的手艺值得赞美——他们将一小块银和一颗宝石变成了一件有价值的珠宝。让我们再思考一下正在考虑的资产。我们可以看到金属块和宝石已经被结合或聚合成戒指。我们还注意到戒指是一种不同于作为输入的宝石或银块的资产。我们可以看到这些输入经历了异质转换,因为输出资产是不同类型的。

这些聚合和分割过程在许多资产的生命周期中都可以看到。在制造业生命周期中非常流行,但使用无形资产。例如,我们在并购中看到它,其中公司可以合并在一起,或者收购,其中一家公司通过并入另一家公司而停止存在。分拆分立的反向过程被清晰地描述为资产的分割。

详细描述交易中资产的生命周期

让我们考虑资产如何在它们的生命周期中移动。我们已经学到资产是如何创建、转化和最终停止存在的。尽管生命周期是一个非常有用的概念,但这些步骤似乎有些受限。毫无疑问,对资产在其生命周期中经历的一系列步骤进行更丰富的描述是有可能的。答案是肯定的!交易为描述资产随时间演变提供了丰富的、特定领域的词汇。例如,保险单被要求、完善、签署、交付、申报索赔、支付索赔、失效或续约。这个生命周期的每一步都是一个交易——我们将在下一节中更详细地讨论交易。

最后,与资产一样,参与者可以通过交易来描述生命周期。那么,你可能会想,资产和参与者之间有什么区别呢?嗯,这实际上归结为思考形式与功能。仅仅因为资产和参与者都可以通过交易来描述生命周期,并且同样如此,这并不意味着它们是相同的东西。就像鸟类、昆虫和蝙蝠都能飞行一样,它们绝对不是相关的。在一般意义上,我们认为参与者和资产是资源——它们仅在最一般的意义上相关。

这就结束了我们对资产的讨论!正如我们在话题末尾看到的那样,交易对于描述资产和参与者的生命周期至关重要,所以现在让我们转向这个主题吧!

介绍交易

到目前为止,我们的旅程涉及了理解商业网络的基本特性——即它由参与者组成,这些参与者参与资产的有意义交换。现在让我们专注于商业网络中最重要的概念——交换。

变化作为一个基本概念

为什么交换是最重要的理念呢?嗯,没有它,参与者和资产就没有了目的!

这似乎是一个过度夸张的说法!然而,如果你稍微思考一下,参与者只有在彼此交换商品和服务(统称为资产)的意义上才有意义存在。如果一个参与者不与另一个参与者交换,那么他们在任何有意义的方式下都不存在。资产也是一样——如果它们不在参与者之间交换,那么它们也不存在任何有意义的方式。如果资产在不同参与者之间不移动,那么资产具有的生命周期就没有意义,因为该资产对参与者来说是私有的,在参与者的私人背景之外对商业网络没有任何意义。

变化,因此,是商业网络中的基本原则。当我们考虑交换、转移、商业、买卖、协议和合同时,所有这些激励性的想法都与商业及其变化的影响有关。变化赋予了商业世界动力和方向。我们捕捉变化的方式是通过交易。这就是为什么交易是商业网络中最重要的概念——它定义并记录了变化——资产的变化;资产所有权的变化;参与者的变化。每当商业网络中发生任何变化时,都会有一笔交易来记录。

交易定义和实例

术语“交易”通常有两种密切相关但不同的用法,意识到这种差异很重要。我们有时使用术语“交易”来一般性地描述交易中发生的事情。例如,我们可能定义房地产交易涉及买方支付一定金额给房产所有者,以换取房产的所有权和产权的转移。(几乎总是,买方还获得了随后出售房产的权利。)在这种意义上,术语“交易”用于一般性地描述交换过程,描述参与者和资产涉及的交换过程。

“交易”一词的另一个用法是描述特定交易。例如,我们可能会说,2018 年 5 月 10 日,黛西从温彻斯特自行车店以 300 英镑的价格购买了一辆自行车。我们在这里使用术语“交易”来描述特定交易的一个实例。这两种用法非常密切相关,上下文几乎总是清楚表明我们在谈论哪一种。

两种用法之间的基本区别在于前者定义了交易的含义,而后者捕捉了交易的特定实例。在现实世界中,我们经常看到交易实例的例子——每当我们进入商店购买商品时,我们都会收到收据!在我们之前的例子中,黛西可能为她的自行车收到了一张收据。收据可能是纸质的,尽管现在它经常发送到我们的手机或电子邮件地址。这张收据是交易的副本——它是黛西对发生的事情的个人记录。自行车店也会保存交易记录的副本,以供他们自己的会计目的使用。

隐式和显式交易

请注意,像这样的交易通常不会看到显式的交易定义;定义被编码在您与之互动的人、流程和技术中。对于像黛西这样的低风险交易,交易定义是隐式的。只有在存在争议时,我们才能了解交易是如何定义的。例如,如果黛西的自行车链条在几天后断裂,她可能合理地期望链条会免费修复,或者自行车会被更换,或者她会退款。这是黛西确定与温彻斯特自行车店的交易的真实性质的时刻。

看起来这种隐式交易定义只有缺点,但事实上并非如此。首先,每个国家的法律都有关于公平交易的显式概念,这将使黛西在进入交易时有合理的期望。在大多数国家,这被称为诸如《货物销售法》之类的东西,它规定了任何商业交易中涉及的所有交易对手的权利和责任。其次,缺乏显式合同简化了黛西和自行车店之间的互动。鉴于在大多数情况下,自行车在购买后都能表现良好一段时间,收据对于大多数实际目的来说已经足够了。每次进行简单购买时重新陈述每个人都知道的真相既费钱又费时。这种简化是人们经常称之为减少摩擦的一个例子。

对于高风险交易或具有特殊条件的交易,情况大不相同——事先明确交易定义至关重要。如果我们再次看 Daisy 的交易,我们会发现,如果存在争议,就会有其他跟进交易—例如,自行车可能已经更换了链条,或在极端情况下,她可能已经拿回了她的钱。我们可以看到,一般来说,我们需要多个条件交易来描述参与者之间对这种交易的满意互动。这意味着如果 Daisy 得到的是一笔抵押贷款,而不是一辆自行车,就有必要指定若干交易和可执行条件。你可能已经听说过一个用来描述这种交易和条件集合的术语——合同

合同的重要性

对于高价值资产,拥有一份合同是很重要的。它定义了一组相关的交易和它们发生的条件。合同通常围绕着特定资产类型展开,并涉及一组明确定义的参与者类型。如果你看一份现实世界的合同,它包括了一系列关于实例和定义的陈述。在合同的顶部,所有的资产和参与者将被列出,并注明了特定的价值—比如 Daisy(买方)、Winchester 自行车(卖方)、300 英镑(价格)、2018 年 5 月 10 日(购买日期)等等。只有在所有这些类型到实例的映射都被列出之后,合同才会在这些类型、交易和在这些类型发生的条件下定义,而不涉及特定实例值。这就是使合同在一开始看起来有点奇怪的地方—但一旦你能看到参与者、资产、交易及其相应价值的结构,它们实际上是相当容易理解的,这种结构使它们更加强大。

签名

在合同中我们最后看到的是其底部——签名!签名在许多方面是合同中最重要的部分,因为它代表了所有交易对手对其中包含的信息达成一致的事实。当然,在现实世界中我们会看到许多签名。Daisy 的商店收据通常上面有她的签名—可以是实物的,或者是通过私钥的数字签名。在简单交易中,商店的签名实际上是隐含的—他们会在品牌收据上放置交易代码,并保留一份副本用于自己的目的—这满足签名的目的。

然而,对于高风险交易,所有交易方都需要明确签署一份合同。更为明确地,为了确保每个交易方都是睁大眼睛参与合同,可能需要一个独立的第三方,如律师、公证人或监管机构,签署合同以验证明交易明确涉及的各方自愿而自由地参与其中。

多方交易处理的智能合约

彻底理解这些概念是绝对至关重要的。它们并不特别复杂,特别是如果你将它们与你每天做的事情联系起来!当涉及理解区块链如何帮助多个交易方创建并就高价值资产相关低摩擦交易达成一致时,我们需要理解这些术语及其重要性,无论是作为独立存在还是相互关联。

现在,当我们看一个商业网络时,我们可以看到它充满了由合同管理的多方交易! 这就是为什么交易是商业网络中最重要的概念;它们定义并捕捉了不同交易方之间有价值资产的约定交换。

现在,让我们来使用一个你在谈论区块链时可能听过很多次的术语——智能合约。它们只是这些概念的数字化表现。智能合约是合同的数字形式——意味着它们可以被计算机系统轻松解释和执行。实际上,所有实现高风险或低风险交易的计算机系统都实现了合同。但是,不同于区块链,这些系统没有内建具有使这些概念转化为技术平台的词汇表。

数字交易处理

正如我们在本章的开头所提到的,这就是基于区块链实现的商业网络的大思想。它们使得从现实世界到计算机系统的转换尽可能简单。尤其是 Hyperledger Fabric 将所有这些想法都表现得非常明确,这样我们就可以轻松地对商业网络进行建模和实现。它保持所有现有概念完整,但是以基本上数字的方式实现它们——使用计算机进程、网络和存储。

交易是商业网络的核心,因为它们作用于资产和参与者。然而,它不仅仅如此。即使我们向商业网络添加更多概念,它们仍然必须始终受到交易的约束。交易性是涉及商业网络所有方面的通用属性。这就像我们在本章前面提到过的能够飞行的能力——商业网络中的每个对象都受到交易的约束,并且必须是交易的主体。

发起交易

暂时停下来,我们可以看到交易通常是由业务网络中的一个参与者发起的。这个参与者通常是从特定服务提供商那里获得服务的消费者。例如,黛西希望在购买自行车时消费 Winchester bicycles 提供的服务。

大多数参与者发起的交易涉及资产状态的变化,但在某些情况下,交易可以涉及参与者状态的变化。例如,如果我通过正式文件更改我的姓名,那么从某种意义上说,被转变的资产就是我——参与者。这强调了交易的核心特性——无论对象如何,它们都捕捉到变化。

交易历史

当我们之前讨论资产的来源时,我们看到资产的历史很重要——它为网络中的参与者提供了信心,并增加了信任。同样,交易历史也很重要,因为它也增加了信任。为什么?嗯,这归结于那些签名。任何变化都必须得到涉及交易的所有参与者的同意,而每个交易中的签名提供了信心,即每个交易方都同意了交换。交易历史更好——它显示了在所有时间点,网络中的每个参与者都同意了每笔交易描述的每个变化!

区块链历史包含一系列交易的顺序。尽管顺序似乎意味着交易按时间顺序发生,但这只是部分正确的。例如,如果我在上午 11 点向我的银行账户存钱,然后在上午 11 点 30 分从我的银行账户支付款项,那么第一笔交易发生在第二笔交易之前是非常真实的。

同样,如果你在上午 11 点向你的银行账户存钱,然后在上午 11 点 30 分支付款项,你的交易就有了明确的顺序。但是,现在让我们问一下,我们的上午 11 点的交易是先发生还是后发生的?或者我们的上午 11 点 30 分的交易呢?即使在某种意义上,我的上午 11 点的交易在你的上午 11 点 30 分的交易之后记录,这是否重要?

交易流

这个例子告诉我们,在讨论交易历史时,交易的依赖性是重要的;依赖于先前交易的交易在其之后记录。对于独立交易流,这种排序就不那么重要了。我们必须小心一点,因为交易有一个讨厌的习惯,就是它们会彼此纠缠在一起。例如,如果你的上午 11 点 30 分的交易将款项支付到我的银行账户,那么两个看似独立的交易流就开始相互干扰了。这意味着我们不能随意延迟交易的记录。

请注意,我们谈论的不是交易的实际发生——在特定时间或特定地点——而是该交易记录在交易历史中。这有点像一本奇怪但全面的历史书,记录了拿破仑在 1800 年前往意大利的行程,同时也记录了 1800 年美国国会图书馆的成立,以及日本人物本居宣长完成的文学作品《古事记传》。重要的是这些事件被记录下来——它们在书中相对于彼此的顺序并不是至关重要的,只要它们大致同时出现即可。

将交易分开到不同的商业网络

这个看似刻意的交易历史例子实际上为我们提供了对商业网络设计的深刻见解——复杂交互网络中所有互动的一条记录并不是一个好主意。这个例子开始说明,将商业网络与特定关注点相关联可能是更好的设计,而不是试图将所有历史记录合并到一个网络中。在我们的类比中,最好为法国历史、美国历史和日本历史各有不同的历史书,并相互交叉参考!

这个想法对于如何处理区块链网络有具体且重要的影响。将商业网络分开成不同的关注点,并将它们链接在一起不仅是良好的设计,而且是必要的设计。这将导致更简单、更易理解、更可扩展、更可扩展和更具韧性的系统。你将能够从小处开始,然后逐步发展,并且有信心无论事物如何演变,你都能应对变化。你将看到 Hyperledger Fabric 明确支持使用称为网络和通道的概念的多个商业网络的想法,我们稍后会更详细地讨论这些内容。

交易历史与资产状态

更详细地审视商业网络历史,我们可以看到一个资产(或参与者)历史的两个元素,即其当前值和导致该值的一系列序列化交易。如果我们按照影响它的所有交易依次从任意时间点进行应用,我们可以生成资产在所有时间点的价值。事实上,我们将交易历史视为一系列在商业网络中不同时间和地点发生的交易事件,从而确定其在任何给定时间点的状态。

我们将在 Hyperledger Fabric 中通过账本世界状态和账本区块链的概念明确表达商业网络的这两个方面。世界状态保存了商业网络中资产的最新值,而区块链则保存了商业网络中所有交易的记录。这使得 Hyperledger Fabric 比其他区块链更强大一些——像它们一样,它记录了区块链中的所有交易。此外,它还计算了资产的当前值,使得非常容易确信你正在处理最新的状态。这些最新的值往往是最重要的,因为它们代表了世界的当前状态。而且,当涉及到发起新交易时,大多数参与者都对此感兴趣。

商业网络作为交易历史

从一个非常真实的意义上来说,我们可以将商业网络视为交易的历史。我们这样说是什么意思呢?好吧,我们已经看到,商业网络由参与多方交易资产交换的参与者组成,这些交易由合同定义。然而,如果我们稍微重新调整一下自己的思路,我们会发现,网络是其交易历史的产物,而这又与发起交易的资产和参与者无法分开。

所有这些概念都是一个整体的组成部分,彼此支持和加强。参与者只是我们理解的第一步——进入商业网络世界的入口。通过学习更多,我们意识到交易实际上是中心,同时也是毫无意义的,除非它们涉及到网络内部的资产和参与者,它们既创造又改变并描述了!正是交易历史将一切联系在一起形成一个连贯的整体,从这个意义上说,它就是商业网络。

监管机构和商业网络

关于一种特殊类型的参与者的最后一句话,这种参与者几乎在每一种商业网络中都很常见——监管机构。大多数商业网络的性质是存在一种角色的参与者,其职责是确保交易遵守某些规则。例如,在美国,证券交易委员会SEC)确保执行涉及证券资产的交易的参与者按照约定的法律和规则进行,使投资者对股票市场充满信心。或者,在英国,驾驶员和车辆许可局DVLA)确保车辆按照英国法律进行适当的保险、税收和交换。另一个例子是在南非,南非食品科学与技术协会SAAFoST)确保涉及农业、食品分销、食品加工和食品零售的交易符合适当的南非法律。

每个商业网络都有某种形式的监管机构来确保适当的监督。简单来说,监管机构确保每个人都按照商业网络的规则来玩游戏。我们可以看到,在所有交易都以数字形式记录在区块链上的商业网络中,监管机构实际上可以更有效地和及时地执行他们的工作。

当然,人们可能会问,如果所有交易对适当授权的参与者都可见,并且能够证明行为的正确性或错误性,那么我们为什么需要监管机构呢?答案是监管机构有权制裁网络中的某些参与者——特别是将他们排除在网络之外,并没收他们的资产或非法交易的资产。这些制裁是网络中最强大的交易,因为它们提供了最终的权力,并且必须仅在极端情况下使用。

祝贺!既然你已经走到了这一步,你真正理解了商业网络的基本性质。更好的是,我们在讨论商业网络时真正需要涵盖的概念只剩下一个:事件。让我们继续讨论你会发现非常有力的商业网络的最后一个方面。

从使用 Composer 设计商业网络的角度讨论事件

到目前为止,我们已经看到商业网络的词汇包含一组紧密联系的概念——参与者、资产和交易。尽管数量不多,但这些概念非常具有表现力——它们包含着重要的想法,有许多方面支持和加强彼此。

并不是说有什么缺失,而是通过添加一个额外的概念,我们将显著增加这个词汇的描述和设计能力。这个最后的概念是事件——混合物中的最后一种成分!好消息是,你可能以前听过这个术语,并且它支持的许多想法是相当明显的。但不要误解,事件是一个非常强大的概念,值得花点时间来掌握——你对这个话题的投资将得到丰厚的回报。

一个通用的概念

我们将事件视为表示特定事实发生或发生的事件。例如,总统抵达澳大利亚,或者股市今天收盘上涨 100 点,或者卡车抵达配送中心都是事件的示例。基本想法似乎相当简单——事件是发生重要事情的时刻。事件代表某种过渡——将世界从一个状态转移到完全不同的状态。这就是事件的本质——历史从一条平滑的线变成了一组连接的点——其中每个点代表一个重要事件。

在商业网络领域,我们可以随处看到事件。发起交易的参与者算作事件。资产经历一系列变化也算作事件。同样地,资产在参与者之间交换也算作事件。资产的生命周期不过是一系列事件的历程!我们现在看到参与者加入和离开业务网络也算作事件。考虑一下交易历史,我们把它看作关于参与者和资产的一系列事件。天哪,一旦我们睁开眼睛,事件真的无处不在!如果我们不小心,这些小空间入侵者会让我们不知所措!

消息携带着事件通知

我们将消息视为事件通知的载体。在现实世界中,我们通过短信、电子邮件或可能是新闻源接收到事件的通知。因此,我们区分事件和其通信。这是一个非常重要的区分,因为它说明我们通过媒介与事件耦合在一起。

让我们现在种下这个想法——我们之后会回到这个想法——即使有一个单独的事件,多个参与者可以通过单独的消息通知。我们看到事件生成者和事件消费者之间存在松散耦合。这一切意味着事件具有稍微难以捉摸的特质——它们稍微抽象的本质使它们难以捉摸,除非通过感知它们的消息。

现在可能需要有点小心了——如果我们过于沉迷于事件,就会失去对重要事情的关注。首先,显而易见的是,我们只需要考虑重大事件——可能会导致某种行动的事件。除了事件之外的一切都只是噪音——我们不需要考虑它。当然,什么算作重大事件将是与领域和问题特定相关的——在金融网络中,股市价格的上涨是重大的,但在教育网络中不是。所以现在,让我们把事件作为一种工具,用于在商业网络中发生重大事情时,当我们需要了解是什么促使参与者行动时。让我们看看如何利用这个工具。

一个例子阐述事件结构

以股票市场事件为例。每当一支股票的价格上涨或下跌,我们可以将其表示为一个事件。例如:

**在 UTC 时间: 2018-11-05T13:15:30:34.123456+09:00 ** 股票 MZK 从 13000 日元上涨了 800 日元

我们可以看到,这是一个关于股票 ABC 在 2018 年 11 月 5 日某个非常特定的时间上涨了 800 日元的事件描述。

就像资产和参与者一样,我们可以看到术语“事件”既可以指事件的类型,也可以指事件的实例。在我们的示例中,我们将类型和实例信息合并在一起。该事件具有类型股票市场 标记,结构包括时间:2018-11-05T13:15:30:34.123456+09:00,符号:MZK,货币:JPY,前值:13000,变化:+800。对于结构中的每个元素,我们显示了此事件的特定实例。从这个事件中,我们可以清楚地看到以结构化形式发生了什么。

事件和交易

我们可以看到,事件与交易密切相关。事实上,因为事件通常描述一笔交易,因此常见到这些术语互换使用。然而,事件描述的活动类别比交易更广泛。具体来说,事件描述了一种变化,而交易则捕获了变化的记录要素。交易往往是外部事件的结果——这是不由特定参与者或资产行动引起的事件。在这种情况下,所产生的交易使用外部事件的信息子集作为输入。但是,事件本身不是交易的一部分,除了在这种有限意义上。这需要一点思考——我们真的在剖析一些微妙但重要的差异。

在可能看起来是矛盾的情况下,交易也可以生成事件!天哪,这似乎变得很复杂!但是想一想——事件只是描述某事发生的情况,有时事件是由交易显式创建的,而不是由任何交易之外的力量引起的。在我们的股票标记示例中,交易可能会生成一个事件,以表示 MZK 股票在单个标记中增长超过 5%!这个事件可能是快速股票上涨,其结构为符号:MZK,增益6.1% ——它是由交易显式生成的。交易体现了业务流程的一部分,即识别和传达高百分比股票变动。这个事件在很大程度上实际上是交易的一部分。

外部事件与显式事件

因此,我们可以看到,事件分为两类——外部事件显式事件。我们不经常将这两个术语视为对立的,但它们完美地描述了业务网络中的两种不同类型的事件。我们的第一种事件类型是外部事件——它是在业务网络外部生成的。此事件将由参与者处理,因此可能会导致交易——不要忘记,只考虑重要事件——会导致行动的事件。对于外部事件,大部分事件内容都被捕获为交易输入,但除此之外的事件内容都不会被记住。如果我们想要保存一个外部事件,我们会生成一个显式交易来执行此操作。

显式事件有所不同。因为它们在交易中生成,所以它们自动成为交易历史的一部分。当交易提交到账本时,这些事件将被释放到网络中 - 在那里它们将被任何对它们感兴趣的参与者消耗。在显式事件的情况下,总账本本身就是事件的产生者。

事件引起参与者采取行动

因此,我们可以看到事件之所以重要,是因为它们标识了导致参与者采取行动的变化!就像在现实世界中一样,当事件发生时,人们和组织会得知它,处理其中的信息,并因此产生行动。我们可以看到,事件为参与者采取行动提供了主要的激励刺激之一 - 通常是通过发起新交易,有时是通过生成新事件。

松耦合设计

现在让我们回到松耦合的想法。事件生产者和事件消费者并不直接了解彼此 - 它们被称为松耦合。例如,当一个参与者被添加到业务网络时,现有参与者不需要联系新加入者介绍自己。相反,现有参与者如果感兴趣,就会监听新的参与者事件。同样,如果一个参与者加入一个网络,它不需要联系每个它感兴趣的人和事物,它只是监听它认为重要的事件 - 这些事件可能会导致它采取行动。我们可以看到事件生产者和事件消费者并不明确地了解彼此 - 他们只知道事件 - 因此,通信可以非常容易地增减 - 它更具可扩展性。

现在我们看到,松耦合是事件和交易之间的一个主要区别。交易明确将参与者绑定在一起 - 在交易中,我们命名所有的交易对手。在事件中,我们根本不知道生产者和消费者之间是否以及如何相关。从设计的角度来看,这意味着我们可以创建一个非常灵活的系统。通过事件,参与者可以以几乎无限灵活的方式相互耦合,这确实反映了我们在现实世界中看到的丰富性。

事件的效用

现在我们知道为什么我们将事件添加到了业务网络的定义中。事件使业务网络几乎具有无限的灵活性。欣赏这一点混沌 - 在某种意义上,这可能是不太可分析的,但没关系。现实世界本来就不可分析 - 事件为参与者之间提供了高效的协调机制,以便重要的变化通过多方交易得到协商和记录。

恭喜!记得业务网络的定义吗?

业务网络是参与者和资产的集合,经历由交易描述的生命周期。当交易完成时,事件发生。

我们意识到这几句话可能比起初显得更加强大——它们描述了一个非常丰富的世界。让我们做一个实例来看看这些想法是如何发挥作用的!

实施商业网络

我们已经在商业网络的世界里游览过了,并且看到了多方参与者之间资产的多方交易处理的重要性——这正是这些网络的生命线。事实上,由于当今商业网络的重要性,已经有大量技术被应用于它们的追求。如果你在 IT 领域工作过一段时间,你可能已经听说过企业对企业B2B),甚至可能已经听说过电子数据交换EDI)[协议]。这些术语描述了企业之间如何交换信息的想法和技术。你甚至可能听说过,或者有过,如 AS1、AS2、AS3 和 AS4 这样的网络协议。这些定义了两个组织之间如何交换业务数据的标准机制。如果你没有听说过这些术语也不要担心——关键的要点是,商业网络在今天在一个非常实质的意义上存在,并且有很多技术应用于它们。

实施商业网络意味着什么呢?嗯,当涉及到诸如汽车或设备或重要文件等有形资产的交换时,区块链会在商业网络中捕获资产、参与者、交易和事件的表示。但是,对于无形资产来说,情况有所不同——在某种意义上,资产的不断去物质化意味着它们在计算机系统内的表示与资产本身一样真实。

去物质化的重要性

考虑音乐的情况。一百年前,音乐可能会记录在黄麻胶上,然后通过一系列技术创新,转移到黑胶唱片、CD、数字迷你光盘。每一步都比上一步便宜,质量更高。但是,大约 25 年前,发生了一些不同的事情!第一个 MP3 格式被引入以支持高保真音频捕获。

这是去物质化的一步,与其他步骤完全不同。是的,它更便宜,质量更高,但至关重要的是,它停止了音乐的物理表现形式。这种去物质化模式越来越普遍——金融产品如债券、证券、互换、抵押贷款等主要是以数字形式表示的。越来越多的文件和表格正在数字化——从飞机和火车票等琐碎的例子,到更重要的教育证书、就业和健康记录。这种向数字化的转变意味着区块链比我们可能想象的更具相关性。

所以,当我们在区块链上实施商业网络时,通常会接近处理商业网络中的实际资产。而且,可以说即使在有形资产的情况下,关于资产的信息和资产本身一样重要!这似乎有些夸张,但请稍作思考。假设你拥有一辆车。这辆车需要加油,需要交税,需要维修和投保。它需要每年检测以确保上路安全性。你的车周围有许多经济活动!这意味着关于车辆的信息非常宝贵—事实上,在汽车的寿命内,总运行成本通常是汽车成本的两倍。也许关于车辆的信息比车本身更有价值!?

区块链在 B2B 和 EDI 方面的益处

区块链可以为跨多个组织的企业对企业B2B)信息处理提供更简单、更全面的方法。而电子数据交换EDI)协议仅关注信息的交换,区块链可以在分类帐中存储数据,通过智能合同处理数据,以及通过共识通信和交换数据。区块链提供了对多方交易处理的整体方法。在区块链中,业务网络中的所有处理、数据和通信都可以从一个连贯的系统中访问。这与传统的 B2B 方法形成对比,传统方法中数据、处理和交换由不同的系统管理。这种分离直接导致了在这些系统之间加入信息的重大处理量,以及整体透明度的缺乏。这个过程被描述为协调—确保业务网络的不同部分之间没有重大差异—这是及时和昂贵的。

我们现在看到了在区块链上实施企业网络的好处。与记录资产的一组不同系统以及在其上运行的不同程序相比,现在有了资产及其完整交易生命周期的共享视图。区块链为资产及其生命周期,参与者、交易和事件提供了明确的共享理解。区块链的这种共享性通过增加透明度而增加了信任,并且彻底简化和加快了处理。例如,组织不必定期与其他对手进行协调,以确保他们的系统吻合—因为在区块链中一切始终吻合。

那么,假设我们希望获得区块链对多方交易处理的益处—我们该如何做呢?这将是我们在本章剩余部分关心的事情—基本的架构方法,但主要是设计工具,您可以使用这些工具在商业网络中实现区块链技术平台。

与区块链进行交互的参与者

首先,哪些参与者与区块链进行交互?首先要说的是,在企业网络中,区块链的主要受益者是持有最多数据的参与者,通常是组织。这并不是说个人不能保存区块链账本的实例,但更有可能的是他们将与管理部分区块链的组织进行交互。实际上,他们甚至可能都不知道自己在使用区块链。在组织内部,虽然是个体使用应用程序与区块链进行交互,但至关重要的是,他们是代表组织行事的——他们是组织的代理人。

同样,当涉及到系统和设备参与者时,设备很少会保存区块链账本的副本。这样,设备更像是个体参与者一样。相反,网络中的系统可以代表组织行事,或者在某些情况下,实际上代表组织。那么,系统代表组织意味着什么呢?如果我们想象一个 B2B 系统,那么组织在网络中看起来真的就像是它的 B2B 网关,实质上,网关就是这个组织。这样看来,一个大型系统与区块链账本实例之间有很密切的关联是有道理的。

通过 API 访问业务网络

组织、个人、系统和设备通过一组业务网络 API 与区块链进行交互。我们马上就会看到这些 API 如何创建,但现在知道区块链网络就像普通的 IT 系统一样被消费。差异在于内部——这些 API 是在区块链基础设施上实现的,最终提供了比实际上可能实现的更简单和更丰富的 API 集。然而,区块链 API 的使用者不需要担心这些——他们只需发出 API,并且所需的服务就会自动执行。正在发生的折衷是,区块链基础设施需要业务网络中的组织之间进行更多的协调。他们必须事先就参与者、资产、交易和事件达成一致,以及它们的发展方向。虽然他们在区块链外部可以唯一地处理、存储和传递信息,但他们必须在区块链上达成一致。这就是折衷:前期一致性换取正常运行时业务流程的极端简化的承诺。

从高层次来看,业务网络 API 很容易理解。在车辆网络中,我们可能有诸如 buyVehicle()insureVehicle()transferVehicle()registerVehicle() 等 API。这些 API 是领域特定的——刚刚提到的 API 与商业票据网络中的 API issuePaper()movePaper()redeemPaper() 完全不同。API 必须是领域特定的,因为这样可以使它们对使用它们的网络中的参与者有意义——这些 API 讲述了参与者的语言。

一个 3 层系统架构

这些 API 在一个非常标准的系统架构内运作。通常,最终用户将在其 Web 浏览器或移动设备上运行一个展示层。这将与应用程序服务器层进行通信,使用根据正在开发的整体解决方案定义的 API。这个应用程序层可能在云中运行,也可能在本地系统上运行。这是应用程序的所有应用逻辑所在的地方,它是由区块链提供的业务网络 API 的消费者。该应用程序可能会执行其他工作,比如访问数据库或执行分析,但从我们的角度来看,它是与区块链网络的交互点。它使用区块链 API,而不是最终设备。总的来说,这些 API 在典型的 3 层系统架构结构中运作,包括展示层、应用程序层和资源管理层。

或者,如果我们有一个设备或系统与区块链进行交互,那么它将没有展示层——它将直接使用应用程序 API 或区块链 API。从某种意义上说,设备是展示层,而系统是应用程序层。同样,这是非常标准的。

Hyperledger Fabric 和 Hyperledger Composer

基本的设计方法同样非常简单。我们使用 Hyperledger Composer 对特定的业务网络中的参与者、资产、交易和事件进行建模。然后,我们使用该模型生成区块链智能合约和账本,实现这些元素,并将其部署到使用 Hyperledger Fabric 创建的区块链网络上。我们还使用 Hyperledger Composer 模型生成一组领域特定的 API,以访问在 Hyperledger Fabric 区块链中操纵它们的交易。正如我们所见,这些 API 将由应用程序代表个人、组织、系统和设备使用。

摘要

在本章中,我们已经介绍了业务网络并对其进行了详细的探讨。通过理解参与者、资产、交易和事件的关键组成部分,我们已经看到,在某种意义上,所有的业务网络都共享相同的关注点。

通过对不同类型的参与者进行分类——个人、组织、系统和设备,我们能够正确描述谁发起捕捉业务网络变化的交易。通过理解资产的概念——有形或无形的价值物品,我们能够描述和理解在参与者之间流动的资源,以及它们表达参与者相互交互的原因。理解参与者和资产使我们能够理解如何在交易中捕捉这些变化。最后,事件的概念使我们能够理解网络发生重大变化的时候,并对其采取行动。

我们花了一些时间讨论这些概念是如何使用 API 消耗的,在接下来的章节中,我们将更加专注于这个方面——如何在一个商业网络的真实示例中展示所有这些想法。我们将特别使用 Hyperledger Fabric 和 Hyperledger Composer,这样你就可以看到如何将这些想法应用于实践。

第七章:一个业务网络示例

在本章中,我们将使用一个真实的示例业务网络,结合我们讨论过的所有概念。具体来说,我们将详细介绍 Hyperledger Composer 信用证示例,以便您了解参与者、资产、交易和事件如何在代码中实现。我们将展示业务网络如何被使用、分析、定义,以及如何使用该定义生成 API、测试它们,并将它们集成到示例应用程序中。这将是一个全面的导览,将让您从概念直接进入实施。我们将使用信用证示例,因为它代表了一个经常在区块链相关讨论中被讨论的众所周知的过程。让我们先讨论一下这个流程,然后看看为什么它被用作宣传样例。

信用证示例

因此,我们来到了我们的示例。Alice,意大利 QuickFix IT 的所有者,希望从在美国经营 Conga Computers 的 Bob 那里购买计算机。Alice 将从她的银行 Dinero Bank 申请信用证,这将被 Bob 的银行 Eastwood Banks 接受作为支付形式。

我们将使用在github.com/hyperledger/composer-sample-applications找到的信用证示例应用程序尝试整个流程。该存储库包含多个业务网络的示例应用程序-我们将使用信用证示例。

安装示例

如果你遵循了第三章中的步骤,使用业务场景做准备,你应该已经完成了所有先决条件。现在,在你的 GitHub 账户中 fork 一个样例应用程序的存储库(github.com/hyperledger/composer-sample-applications),然后使用以下命令将其克隆到你的本地计算机:

cd <your local git directory>
git clone [email protected]:<your github name>/composer-sample-applications.git


导航到相应的目录,并使用以下命令安装信用证示例应用程序。应用程序下载和安装需要几分钟时间:

cd composer-sample-applications

cd packages/letter-of-credit

./install.sh

安装脚本还会在你的浏览器中启动应用程序的演示层。让我们来调查一下。

运行示例

你会看到你的浏览器打开了与网络中不同参与者相对应的选项卡。点击不同的选项卡查看网络中的不同参与者。当我们通过示例工作时,我们将扮演这些角色中的每一个。让我们通过尝试应用程序来走一遍这个过程:

第 1 步 - 准备请求信用证

我们首先准备我们的请求:

  1. 在浏览器上选择第一个选项卡-你将看到以下页面:

  1. 您现在是 Alice!您可以看到您的银行和您的账户细节。您可以通过单击“申请”按钮申请一个信用证。试试看!

  2. 您将看到一个页面,您可以在该页面上请求信用证书:

第 2 步 - 请求信用证

这是流程的第一个阶段,您将要求从 Bob 购买计算机的信用证!在每个屏幕的顶部,您都能看到您在流程中的确切位置,例如:

在页面的左侧,您将看到商家的详情 - 阿里斯和 Bob 的联系方式。注意公司名称和账户详情:

让我们假装作为 Alice 提交一个申请。在屏幕的右侧,您可以输入贸易的细节。假设 Alice 向 Bob 请求 1,250 台计算机,每台价格为 1,500 欧元。该申请的总价值为 1.875M EUR:

还要注意,Alice 可以根据(她银行的许可)在申请书上选择一些条款和条件。这些是与 Bob 签订合同的重要条款和条件 - 除非这些条件得到满足,否则双方都不会收到货物或付款:

你可以根据需要编辑这些内容,尽管这些过程不会受到影响。

当您准备好进入流程的下一个阶段时,请点击“开始批准流程”按钮:

恭喜,您刚刚申请了信用证!

第 3 步 - 进口银行批准

这是流程的下一个阶段。在浏览器的下一个标签页上单击。现在您是阿里斯银行 Dinero 的员工 Matias,需要处理她的申请!以下是 Matias 看到的页面:

它显示了来自 Alice 的申请,目前正等待 Matias 的批准。他代表 Dinero 银行行事,并申请批准或拒绝的信用证。我们可以设想,在一个复杂的过程中,Matias 只需要批准无法自动批准的特殊信函。

如果 Matias 点击申请,他将看到与 Alice 请求的内容基本相同的详细信息:

在我们的场景中,Matias 将批准信用证,流程将继续!选择“接受”按钮,我们将进入下一步:

第 4 步 - 出口银行批准

在浏览器的下一个标签页上单击。现在您是 Bob 银行 Eastwood 的员工 Ella,已被告知阿里斯希望与 Bob 做生意:

这个示例对流程稍作创意性处理 - 通常情况下,信函将由 Alice 提交给 Bob。然后 Bob 将其提交给 Ella。然而,我们可以看到,因为每个人都可以预先查看信函,所以流程创新是可能的。我们稍后会详细说明这一点。

我们可以看到,Ella 授权了流程的下一个阶段 - 我们可以看到信函在流程图中的位置。当 Ella 选择了这封信时,她可以看到以下详情:

请注意货币已经被更改。Alice 必须用美元支付,因为那是 Bob 想要的,但是 Ella 和 Matias 已经就 Alice 和 Bob 的汇率达成了一致,所以每个人都可以使用自己的货币。Alice 将以欧元计价,而 Bob 将以美元支付。

在屏幕顶部,您将看到与流程相关的以下信息。我们可以看到我们在流程中的位置;由于区块链的唯一性,增加的透明度是可能的,尽管不同的组织每个都通过自己的系统托管和批准了流程的各个阶段:

让我们再次推进流程。Ella 可以通过点击接受按钮来批准该信函:

第 5 步 - 出口商收到信函

在你的浏览器中点击下一个选项卡。你现在是 Bob,你可以看到 Alice 的信用证:

在这个流程示例中,Bob 可以相当确信 Alice 是值得信赖的,因为他的银行事先告诉了他。如果 Bob 选择这封信,他将被显示其详细信息:

希望你现在开始理解这个流程了 - 所以让我们不要再一次详细说明所有细节了!只需注意 Bob 如何因为他可以获得的透明度而增加信任。Bob 接受信函作为支付(点击接受),现在必须将货物发给 Alice!

第 6 步 - 装运

你将会回到 Bob 的初始界面,但是请注意,现在有一个选项将货物运送给 Alice:

点击发货订单,表示货物已经运送给 Alice:

Bob 现在可以看到,就信用证流程而言,他已经完成了 - 订单已经发货。

但是 Bob 还没有收到付款 - Alice 必须先收到货物才能发生这种情况。请注意 Bob 网页右下角的历史记录。Bob 可以看到他在整个流程中的位置,以及在他收到付款之前需要完成的一些步骤:

让我们返回到 Alice 的选项卡,继续流程的下一步。

第 7 步 - 收到货物

返回到你浏览器中 Alice 的选项卡:

当爱丽丝从鲍勃那里收到电脑时,她可以点击“接收订单”来表示这一点,并查看信用证。此时,两家银行都能释放付款。让我们转到马蒂亚斯的网页,看看这个过程的下一步。

第八步 - 付款

马蒂亚斯可以看到爱丽丝和鲍勃很高兴,因此付款可以进行。点击马蒂亚斯的初始页面,查看当前信函的详细信息:

马蒂亚斯可以看到爱丽丝已经收到货物,马蒂亚斯可以点击“准备付款”来进行下一步。

第九步 - 关闭信函

爱拉现在可以关闭信函并向鲍勃付款:

作为艾拉,点击“关闭”以进入流程的最后一步。

第十步 - 鲍勃收到付款

如果我们返回鲍勃的网页并刷新它,我们可以看到鲍勃有一些好消息!看看他的余额增加了:

鲍勃现在已经收到了他向爱丽丝出售的电脑的付款。业务流程已经完成。

概括流程

爱丽丝想从鲍勃那里购买电脑,并使用信用证流程来促成这次交易。她以美元购买商品,但以欧元支付。在支付之前,她能够确信货物符合她的条款和条件。

鲍勃向爱丽丝出售了电脑,一个他之前不认识的海外客户。信用证流程使他有信心以他本地货币美元的形式收到他货物的付款,只要爱丽丝对货物满意即可。

马蒂亚斯和艾拉,分别是 Dinero Bank 和 Eastwood Bank 的代表,提供了一个系统,让爱丽丝和鲍勃能够相信彼此将履行相互同意的条件以便收到付款。他们能够为他们的服务向爱丽丝和鲍勃收取公平的价格。他们几乎实时了解业务流程的每一个步骤。

现在让我们看看如何使用 Hyperledger Composer 和 Hyperledger Fabric 实现这个过程。

分析信用证流程

业务网络的核心是一个业务网络定义,其中包含资产、参与者、交易和事件的正式描述。我们将为信用证申请检查这一点。在本章结束时,您将能够了解网络是如何实现和被应用程序访问的。此外,您将具备构建自己的网络和消费应用程序的知识。

操场

如果您转到演示中的下一个选项卡,您会发现 Hyperledger Composer Playground 已经为您打开:

游乐场是一个工具,可以让您调查商业网络。游乐场的初始视图包含一个充满 商业网络卡钱包。就像一个真实的钱包一样,这些卡可以让您连接到不同的网络。当您使用特定的卡连接到网络时,您就会作为不同的参与者进行操作。这对于测试网络非常有用。让我们作为管理员连接到网络,看看里面有什么!(稍后我们将创建我们自己的网络卡。)

查看商业网络

在标记为 admin@letters-credit-network 的商业网络卡上,点击立即连接。您将看到一个网页:

商业网络定义视图

这是商业网络定义的视图。它包含我们在信用证网络中讨论的参与者、资产、交易和事件的定义。页面的左侧是一组文件,其中包含与我们连接的网络相关的这些概念的信息。我们选择了 关于,在右侧,我们可以看到商业网络的描述。让我们稍微详细地研究一下这个描述——理解这一点非常重要。

商业网络描述

README 文件包含了对网络的自然语言描述,涉及其资产、参与者、交易和事件。

参与者描述

参与者列在商业网络描述中:

Participants
 Customer, BankEmployee

在我们的示例中,有四个参与者 实例 ——Alice、Bob、Matias 和 Ella。但请注意,这里只有两种参与者 类型,即 CustomerEmployee。在我们的网络中,Alice 和 Bob 是 Customer 类型的参与者,而 Matias 和 Ella 是 BankEmployee 类型的参与者。我们可以看到,这些类型是从银行的角度命名的——因为 Dinero 和 Eastwood 银行提供网络服务,并由 Alice 和 Bob 使用。

我们很快会看到有关这些参与者类型及其网络中特定实例的更多细节。但现在,只需考虑一下我们如何将网络中的参与者简化为两种非常简单的表示。尽管我们在应用程序中看到了丰富的行为,但在参与者方面,网络却非常简单。你将在商业网络中看到这一点——尽管参与者可以有很多实例,但类型的数量通常非常有限,并且很少超过 10 个。当然,规则是用来打破的,但你会发现以这种方式思考网络会让分析变得更加可管理。

资产描述

如果你对这个商业网络中参与者类型数量很少感到惊讶,那么当你看到资产类型的数量时,你会感到惊讶:

Assets
 LetterOfCredit

现在,这是一个示例网络 - 这里是为了教我们关于业务网络概念,而不是对信用证世界的详尽表示。 但是,如果你考虑我们的例子,整个流程主要关注的是一个资产类型:信函

公平地说,我们没有关注正在转移的货物 - 电脑或付款。 在真实的系统中,这些将被描述为资产。 即便如此,请注意资产类型的数量仍然相对较少。 我们可以创建无限数量的信用证实例,计算机和付款,但类型仍然只有几种。

我们稍后将详细了解此资产类型的细节。

交易描述

现在,让我们转移到业务网络中的交易类型:

Transactions
 InitialApplication, Approve, Reject, SuggestChanges, ShipProduct, ReceiveProduct, ReadyForPayment, Close, CreateDemoParticipants

最后,我们可以看到相当多种类型! 这是典型的情况 - 尽管参与者和资产类型的数量相当有限,但资产的生命周期丰富多彩。 如果你考虑我们的应用程序,信用证与网络中的不同参与者交互时会经历许多状态。 这些交易直接对应于这些交互。(忽略CreateDemoParticipants,这是设置演示的交易!)

交易名称相当容易理解 - 这些与信函的生命周期密切相关。 这是您作为不同参与者使用应用程序时经历的步骤。 Alice 进行了InitialApplication,并有可能对信函的条款和条件进行SuggestChanges。 Mattias 和 Ella 可以ApproveReject 信函。 Bob 调用ShipProduct 表示他已完成了他的交易,并且 Alice 使用ReceiveProduct 也是这样表示她已经收到了计算机。 最后,Matias 表示该信函已准备好进行支付,并且 Ella 发出了Close 交易以结束流程并触发向 Bob 的付款。

没有理由交易类型的数量必须大于资产类型的数量。 人们很容易想象许多不同类型的资产,其生命周期相同且相对简单。 想象一下零售商的产品库存 - 商品可以被采购,交付,出售和退货。 这是一个相对简单的生命周期,但是不同类型的商品数量可能相当大。 然而,我们可能期望这些不同的商品通过某种行为的共性共享这种生命周期; 毕竟,它们都是产品。 关于继承的这种想法以后会有更多内容。

我们将更详细地研究这些交易的实现,但目前,最重要的是理解网络中参与者之间资产流动的概念图片,如交易所描述的,而不是担心这些交易变化背后的确切逻辑。

事件描述

最后,让我们看一下业务网络中事件的列表:

Events
 InitialApplicationEvent, ApproveEvent, RejectEvent, SuggestChangesEvent, ShipProductEvent, ReceiveProductEvent, ReadyForPaymentEvent, CloseEvent

我们可以看到事件的名称与交易类型匹配,这是典型的。这些是显式事件,由交易生成,以指示业务网络中发生某些事件的时间。在我们的场景中,它们被用户界面用于保持网页的最新状态,但当然也可以用于更复杂的通知处理,例如,CloseEvent 可以用于触发向 Bob 的支付。

当您首次定义业务网络时,您会发现事件与交易密切相关。但是,随着时间的推移,您会发现更复杂的显式事件被添加,例如,Matias 或 Ella 可能想要为 HighValue 信函或 LowRisk 申请生成特定事件。

我们稍后会详细讨论这些事件的细节。

一个业务网络的模型

现在我们已经理解了自然语言中业务网络中的类型,让我们看看它们在技术上是如何定义的。在 Playground 的左侧,选择模型文件。

在此业务网络中,只有一个模型文件定义了参与者、资产、交易和事件。在更大的应用程序中,我们将保留来自不同组织的信息在其自己的文件中,并且通常在其自己的命名空间中。它允许它们保持分离,但在必要时将它们汇集在一起。让我们看看命名空间是如何工作的。

命名空间

我们的示例使用单个命名空间:

namespace org.acme.loc

此命名空间表示此文件中的类型定义已由 Acme 组织的信用证流程定义。这都是一个简短的名称!使用命名空间——它们将帮助您清楚地分离,更重要的是传达,您的想法。建议使用分层名称,以便清楚地知道网络中哪些组织正在定义网络使用的相关类型。

枚举

接下来,我们看到一组枚举类型:

enum LetterStatus {
  o AWAITING_APPROVAL
  o APPROVED
  o SHIPPED
  o RECEIVED
  o READY_FOR_PAYMENT
  o CLOSED
  o REJECTED
}

这些是信函要经过的状态。当我们访问一封信时,我们将能够使用此枚举标识业务流程的位置。所有的名称都相当自我解释。

资产定义

现在我们来到了第一个真正重要的定义——信用证资产:


 asset LetterOfCredit identified by letterId {
   o String letterId
   --> Customer applicant
   --> Customer beneficiary
   --> Bank issuingBank
   --> Bank exportingBank
   o Rule[] rules
   o ProductDetails productDetails
   o String [] evidence
   --> Person [] approval
   o LetterStatus status
   o String closeReason optional
 }

让我们花点时间来理解这个定义,因为它既是理解业务网络的中心,也是特别关注 Hyperledger Composer 的。

首先,请注意asset关键字。它表示接下来的内容是描述资产的数据结构。这就像正常编程语言中的类型定义,但具有一些特殊特征,我们稍后会看到。

我们可以看到资产是LetterOfCredit类型。在此示例中,我们只有一种资产类型——在更复杂的示例中,我们将拥有更多类型的资产。例如,我们可以扩展此模型以包括 Shipment 资产和 Payment 资产:

asset Shipment
asset Payment 

现在,让我们跳过identified by子句,转到资产定义中的第一个元素:

o String letterId

字母o表示这个字段是资产的简单属性。这是一种略显奇怪的表示方法,所以可以将它看作是一种装饰。第一个属性是letterId。回想一下,当在商业网络中创建一封信时,将为其分配一个唯一 ID。如果回想一下,在我们的例子中,我们有letterId分别是L64516AML74812PM。这是通过字段具有String类型来表示的——有很多类型可用,我们很快就会看到。我们可以看到,这个定义允许我们将一个可读的标识符与资产关联起来。请注意,这个标识符必须是唯一的!

现在让我们回到identified by子句:

identified by letterId

现在我们可以理解,这表明letterId属性是唯一标识资产的属性。这是一个简单但强大的想法,与现实世界密切相关。例如,一辆汽车可能有一个用于唯一标识它的车辆识别号VIN)。

让我们继续下一个属性:

--> Customer applicant

我们注意到的第一件事是-->装饰符!(在您的键盘上键入两个破折号和一个大于符号)。这是一个引用属性——它指向某物!就一封信而言,它指向一个不同的类型,Customer,而这个元素的名称是applicant。看到引用的概念比我们之前看到的简单属性复杂一些——因为它做的工作更多。这个字段表示这封信有一个申请者,是Customer类型,而且你需要通过这个引用查找它。

在我们的示例中,一封信的实例将指向Alice,因为她是 Dinero 银行的客户,她提出了申请。请注意,这个引用属性指向商业网络中的不同对象。这种引用的概念非常强大——它允许资产指向其他资产,以及参与者,参与者也是如此。有了引用,我们能够表示我们在世界中看到的丰富结构。这意味着我们可以创建可以组合和分割的资产,对参与者也是如此。在我们的示例中,我们使用引用来查看谁申请了一封信,通过导航引用。再次看到,这个模型非常以银行为中心。稍后我们将看到Customer实际上是一个参与者,并且我们将看到像 Alice 这样的参与者是如何定义的。但现在,让我们先留在资产定义上。

正如我们在商业网络中所讨论的,我们的应用程序使用了一种简单的方式来建模所有权——在现实世界中,这经常是一种联想参考。我们可以将这种更复杂的联想关系最容易地建模为一个OwnershipRecord,它指向一个资产,并且如果需要的话,指向一个参与者:

asset OwnershipRecord identified by recordId {
   o String recordId
   --> LetterOfCredit letter
   --> Customer letterOwner

我们可以立即看到这种方法的强大之处。我们能够建模现实世界中存在的关系,使我们的应用程序更加现实和易于使用。对于我们的目的,我们当前的模型完全够用。

让我们转向下一个字段:

--> Customer beneficiary

这是与上一个领域非常相似的领域,在我们的示例中,这个元素的一个实例将是鲍勃。没有必要花时间来定义这个概念。当然很重要,但它只是指向鲍勃的信。如果你回忆一下,我们的应用程序总是将两个交易对方与一个信件关联起来。

接下来的两个字段具有类似的结构,但我们将花更多时间讨论它们:

--> Bank issuingBank
--> Bank exportingBank

我们可以看到这些字段也是对其他对象的引用,根据它们的名称- issuingBankexportingBank,我们可能会怀疑它们是参与者!这些类型的示例实例是Dinero 银行Eastwood 银行,它们代表着 Alice 和 Bob。

通过这四个参考字段,我们已经建模了资产的非常丰富的结构。我们表明信用证确实有四个参与者参与其中。我们给它们起了符号化的名称和类型,并展示了它们如何与资产相关联。此外,我们没有编写任何代码就完成了这一点。稍后我们需要做一些这样的事情,但现在,请注意我们在模型中捕捉到了信用证的基本本质。值得花一点时间真正理解这一点。

我们只会考虑资产定义中的另一个字段,因为希望你现在已经掌握了这个!这是一个重要的字段:

o LetterStatus status

还记得文件顶部定义的 ENUMs 吗?很好!这个字段将包含那些不同值,例如AWAITING_APPROVALREADY_FOR_PAYMENT。您在您的业务网络中经常,如果不是总是,会有这样的字段和枚举,因为它们以一种非常简单的形式捕捉了您模拟的业务流程中的位置。如果您熟悉工作流程或有限状态机,您可能想把这些看作状态-这是一个非常重要的概念。

参与者定义

现在我们转向模型文件中的下一组定义:参与者!

让我们看看第一个参与者定义:

participant Bank identified by bankID {
  o String bankID
  o String name
}

这是我们的第一个participant类型定义,一个银行。在示例应用程序中,我们有两个这种类型的实例:Dinero 银行Eastwood 银行

我们可以看到,参与者是通过participant关键字identified by的,随后是类型名称-银行。在这种情况下,参与者类型是一个组织,而不是一个个人。与资产一样,每个参与者都有用于识别的唯一 ID,我们可以看到对于银行来说,它是bankID字段:

participant Bank identified by bankID

对于我们的示例,银行的建模非常简单-只有一个bankID和一个name,它们都是字符串:

String bankID
String name

我们可以看到银行实际上比信件简单得多。不仅仅是因为它们具有较少的字段和更简单的类型。更重要的是,它们不引用任何其他参与者或资产——这就是使它们简单的原因——缺乏引用,简单的结构。你的模型也将是如此——一些资产和参与者将具有相对简单的结构,而其他人将具有更多,包括对其他资产和参与者的引用。

记住这些类型是从资产定义中调用的。如果需要,再次查看信件类型定义,看看引用:

--> Bank issuingBank
--> Bank exportingBank

现在你能看到信件 资产银行 参与者是如何相关联的了吗?太好了!

现在让我们看看下一个参与者类型。它和我们之前看到的有点不一样,现在先忽略抽象关键词:

abstract participant Person identified by personId {
  o String personId
  o String name
  o String lastName optional
  --> Bank bank
}

感觉我们的应用程序中有Person类型的四个实例——Alice 和 Bob,Matias 和 Ella!让我们来看看个体参与者是如何定义的:

abstract participant Person identified by personId

再次,忽略抽象关键词。该语句定义了 Person 类型的参与者,由其类型定义中的唯一字段identified by。这些类型将是我们应用程序中的个体参与者,而不是之前我们定义的组织(即银行)。(我们可能期望BankPerson在结构上相关,我们以后会看到!)

如果我们更详细地看一下定义,就会发现他们的结构比 bank 有趣多了:

o String personId
o String name
o String lastName optional
--> Bank bank

我们可以看到Person也有名字和姓氏。但注意姓氏是optional的:

o String lastName optional

我们可以看到 optional 关键字表示 lastName 可能存在也可能不存在。你可能还记得我们的示例中,Alice 和 Bob 提供了姓氏(Hamilton 和 Appleton),但银行的员工 Matias 和 Ella 没有。这种选择性已经被建模了——看看它是如何帮助我们使应用程序更接近现实世界的。

然而,最重要的字段是接下来的一个:

 --> Bank bank

为什么?它揭示了结构。我们可以看到一个人与银行有关。对于 Alice 和 Bob 来说,这是他们有账户的银行。对于 Matias 和 Bob 来说,这是他们的雇主。我们将再考虑一下,这是否真的是正确的建模关系的地方,但目前重要的是,我们有一个个体参与者与组织参与者有关。你可以看到复杂结构不仅仅适用于资产——参与者也可以有复杂结构!

但等等,这并不是那么简单。在定义中我们跳过了一些东西,是吗?看看下面的内容:

abstract participant Person identified by personId { 

抽象 关键字几乎完全摧毁了我们刚说的关于Person类型的一切!抽象类型很特殊,因为无法实例化。真的吗?鉴于我们可以看到 Alice 和 Bob,Matias 和 Ella,这似乎有违直觉。

要理解发生了什么,我们需要转向下一个参与者定义:

participant Customer extends Person {
   o String companyName
}

仔细看看这个定义的第一行:

participant Customer extends Person {

我们可以看到我们定义了一个称为Customer的特殊Person类型!这比以前更好,因为 Alice 和 Bob 都是Customers。实际上,在我们的应用程序中,我们没有Person参与者的实例 - 我们有Customer类型的实例。

我们现在可以看到Customer类型定义中的extends关键字与Person类型定义中的abstract关键字相配对。它们是我们之前提到的类型特化和继承这一更大概念的一部分:

abstract participant Person
participant Customer extends Person 

abstract关键字阻止我们定义Person的实例!这很重要,因为在我们的例子中,实际上是正确的 - 没有Person类型的实例,只有Customer类型的实例。

我们可以看到一个Customer在扩展Person类型时有一个额外的属性,即他们的公司名称:

o String companyName

对于 Alice,这将是 QuickFix IT,对于 Bob,将是 Conga Computers。

最后,让我们看看最后一个参与者类型,BankEmployee

participant BankEmployee extends Person {
}

我们不需要详细描述这个 - 你可以看到,例如CustomerBankEmployee扩展了Person类型,但不同于它,它没有添加任何额外的属性。这没关系!在我们的应用中,Matias 和 Ella 是这种类型的实例。

我们现在可以看到为什么Person类型是有用的。不仅仅是因为它不能被实例化,它还捕捉到了CustomerBankEmployee之间的共同之处。它不仅仅是节省了打字的工作 - 它展示了一个内部结构,这提高并反映了我们对业务网络的理解。

牢记这一点,你可能会考虑是否以以下方式对模型进行略微更加现实的建模:

abstract participant Person identified by personId {
   o String personId
   o String name
   o String lastName optional
}

participant Customer extends Person {
   o String companyName
   --> Bank customerBank
}

participant BankEmployee extends Person {
   --> Bank employeeBank
}

在现实情境中,实际参与者的身份将被存储在模型之外。这是因为个人身份和不可更改的账本不是一个好的组合。在账本上存储 Alice 的个人信息意味着它将永远存在。

你能看出这个模型如何展示了CustomerBankEmployee之间的银行关系性质有所不同吗?

这里有一个重要点 - 没有所谓的正确模型。模型只是为了服务于一个目的 - 它们要么足够,要么不足够。对于我们的目的来说,我们的两个模型完全足够,因为我们不需要在他们与银行的关系方面区分CustomersBankEmployees

好了,参与者的部分就到这里。让我们继续下一个模型定义中的元素。

概念定义

观察ProductDetail而不是Rule,因为它一开始会稍微容易理解:

concept ProductDetails {
   o String productType
   o Integer quantity
   o Double pricePerUnit
}

概念在模型中是较小但有用的元素。它们既不是资产也不是参与者 - 它们只是定义其中包含的结构元素。

这个前置概念定义了ProductDetail。我们可能会认为这实际上是一个资产 - 对于我们的应用程序来说,它不是参与者之间转移的东西!当我们看到Rule概念时,可能会更清楚一些,它捕捉了信用证的条款和条件:

concept Rule {
   o String ruleId
   o String ruleText
}

这与资产或参与者不太相似,但将其作为单独的类型很有帮助,因为它揭示了一个重要的结构。

交易定义

让我们继续吧!下一节非常重要 - 交易!让我们先从看第一个交易定义开始:

transaction InitialApplication {
   o String letterId
   --> Customer applicant
   --> Customer beneficiary
   o Rule[] rules
   o ProductDetails productDetails
}

我们可以看到,像资产和参与者一样,交易是用自己的关键字定义的:

transaction InitialApplication {

transaction关键字标识接下来的内容是一个交易类型的定义。就像assetparticipant关键字一样。请注意,交易定义中没有identified by子句。

此交易定义代表了 Alice 为信用证所做的初始申请。这实在是显而易见,不是吗?Alice 使用的应用程序会创建交易的特定实例,并且我们可以看到其中包含的信息:

o String letterId
--> Customer applicant
--> Customer beneficiary
o Rule[] rules
o ProductDetails productDetails

如果你回顾一下 Alice 的网页,你会看到所有这些信息:申请人Alice,受益人Bob,条款和条件规则)和产品详细信息。请注意,申请人和受益人是参与者的引用,而规则和产品详细信息则是概念。

我们可以看到,交易的结构相对简单,但却强大地捕捉了一个申请人(例如,Alice)申请与受益人(例如,Bob)做生意的意图。

事件定义

看一下模型文件中的下一个定义:

event InitialApplicationEvent {
   --> LetterOfCredit loc
}

这是一个事件!你经常会看到这种情况 - 事件定义紧邻同名的交易。这是因为这真的是一个外部事件 - 它只是捕捉申请人申请信用证。它只是指向生成事件的信件。在申请中,它只是用于保持用户界面最新,但通常情况下,这个初始申请可能引发各种处理。

继续浏览模型文件,你会看到为流程的每个步骤定义的交易和事件,有时还有与该交易步骤相关的额外属性。花点时间看看这些 - 它们很有趣!

正如我们所见,还可以声明更明确的事件,比如高价值信件或低风险申请。想象我们的应用程序用以下事件来做到这一点:

event highValueLetterEvent {
   --> LetterOfCredit loc
}

 event lowRiskLetterEvent {
   --> LetterOfCredit loc
}

你认为模型文件中的哪些交易会与这些相关联?

要确定这一点,我们需要考虑这个过程 - 一个高价值的信件在申请后立即被知晓,因此它将与 InitialApplication 交易相关联。然而,直到交易被两家银行首次处理,并且申请人和受益人都被评估之前,很难说这封信是低风险的。这意味着这个事件更可能与 Approve 交易密切相关。

此外,在这种更高分辨率的情况下,我们会考虑为进口银行批准和出口银行批准创建单独的交易,ImportBankApprovalExportBankApproval

检查实时网络

很好 - 现在我们已经看到了业务网络中参与者、资产、交易和事件类型是如何定义的,让我们看看如何创建这些类型的实例。Playground 工具还有另一个非常好的功能 - 它允许我们在业务网络运行时查看网络内部,以查看这些类型的实例,并在 Playground 页面顶部选择测试选项卡:

图像

你会发现视图有点变化。在左侧,我们可以看到为这个业务网络定义的参与者、资产和交易:银行银行员工客户LetterOfCredit,以及交易。你可以选择这些选项,当你这样做时,右侧的窗格会变化。试试看!

选择 LetterOfCredit 资产,在右侧窗格上,你将看到以下内容(使用显示所有展开视图):

图像

哇 - 这很有趣!这是我们申请的一封实际信用证。让我们仔细看看信件,以及它如何映射到我们之前检查的类型结构。

检查信用证实例

我们可以看到 ID,L73021 AM,和实例信息。它显示为 JSON 文档,你可以看到结构与 LetterOfCredit 定义中的结构相似,但其中包含了真实的实例数据。

你可以看到信件中包含的每个资产和参与者都有一个类($class),它由命名空间与类型名称连接而成。例如:

"$class": "org.example.loc.LetterOfCredit"
"$class": "org.example.loc.ProductDetails"

还要注意这封信的信息是如何被捕获的:

"letterId": "L73021 AM"
"productType": "Computer"
"quantity": "1250"

最后,请注意信件处于最终状态:

"status": "CLOSED"
"closeReason": "Letter has been completed."

所有这些数据都非常强大。为什么呢?因为类型和实例信息被保留在一起,就像在真实合同中一样,它在编写后可以被正确解释。你可以想象这对喜欢在数据中寻找模式的分析工具有多么有帮助!

对于参考属性,我们可以看到结构略有不同:

"applicant": "resource:org.example.loc.Customer#alice"
"beneficiary": "resource:org.example.loc.Customer#bob"
"issuingBank": "resource:org.example.loc.Bank#BOD"
"exportingBank": "resource:org.example.loc.Bank#ED"

我们可以看到这些属性是参与者的引用,如果我们点击参与者选项卡,我们就能看到它们!点击银行选项卡:

图像

检查参与者实例

你可以看到我们网络中的两家银行,它们的类型和实例信息!点击不同的参与者和资产选项卡,并检查数据,看看类型如何在场景中被实例化。花些时间来做这个——重要的是你理解这些信息,将它们与类型联系起来,并真正思考它们与业务网络的关系。不要被欺骗——信息看起来很简单——但其中有一些强大的想法需要一些时间去联系。然而,我们鼓励你这样做——真的值得理解一切是如何联系在一起的,这样你也可以做到同样的事情!

检查交易实例

现在点击“所有交易”选项卡:

你可以看到我们应用程序运行过程中完整的交易生命周期。(你的时间可能会有点不同!)如果你滚动查看交易,你可以看到在我们的场景中确切发生了什么——Alice 申请了一封信,Matias 批准了,等等。如果你点击查看记录,你将能够看到单个交易的细节。

例如,让我们来看看 Alice 提出的InitialApplication

我们可以看到交易细节(我们稍微编辑了它们以适应页面):

"$class": "org.example.loc.InitialApplication",
"letterId": "L73021 AM",
"applicant": "resource:org.example.loc.Customer#alice",
"beneficiary": "resource:org.example.loc.Customer#bob",
"transactionId": "c79247f7f713006a3b4bc762e262a916fa836d9f59740b5c28d9896de7ccd1bd",
"timestamp": "2018-06-02T06:30:21.544Z"

注意我们如何可以看到这笔交易的确切细节!再次,非常强大!花一些时间查看此视图中的交易记录。

向网络提交新交易

我们可以在 Playground 中做更多的事情;现在我们将动态地与业务网络进行交互!

确保你已经在测试视图中选择了LetterOfCredit资产类型。注意左侧窗格上的“提交交易”按钮:

我们将通过提交新的LetterOfCredit申请与业务网络进行交互。如果你按下提交交易,你将看到以下输入框:

在交易类型下拉菜单中,你会看到列出了所有可能的交易。选择InitialApplication,并用以下数据替换 JSON 数据预览:

{
   "$class": "org.example.loc.InitialApplication",
   "letterId": "LPLAYGROUND",
   "applicant": "resource:org.example.loc.Customer#alice",
   "beneficiary": "resource:org.example.loc.Customer#bob",
   "rules": [
     {
       "$class": "org.example.loc.Rule",
       "ruleId": "rule1",
       "ruleText": "This is a test rule."
     }
   ],
   "productDetails": {
     "$class": "org.example.loc.ProductDetails",
     "productType": "Monitor",
     "quantity": 42,
     "pricePerUnit": 500
   }
 }

你能看到这个交易描述了什么吗?你能看到 Alice 和 Bob 之间的新LetterId作为CustomerBeneficiary吗?你能看到ProductDetailsQuantityPrice吗?

如果你按下提交,你会看到你被返回到主视图,并且一个新的信函已被创建:

恭喜,你刚刚提交了一份新的信用证申请!

但等等!如果我们已经与实时网络交互,那么当我们返回到应用视图时会发生什么。如果你回到了 Alice 的视图,你会注意到她有一封新的信:

Hyperledger Composer Playground 已经让我们与实时业务网络互动!此外,如果我们选择 Matias 的页面,我们可以看到该信件正在等待批准:

注意所有属性都是您在样本交易中输入的属性!您现在可以使用 Playground 将此信件移动到其完整生命周期。我们建议您花一些时间这样做——这将帮助您巩固您的知识。

理解事务如何实现

这一切都非常令人印象深刻,但它是如何工作的——实现这些操作的逻辑在哪里,操作参与者和资产,并创建事件?要理解这一点,我们需要查看事务程序——在提交到引用这些资产、参与者和事件的网络的事务时运行的代码。

事务代码保存在一个脚本文件中,如果您在 Define 选项卡上选择 Script File,您将看到以下内容:

这就是实现事务的代码!今天,Hyperledger Composer 使用 JavaScript 来实现这些函数,这就是您在此页面上看到的内容——JavaScript。如果您浏览脚本文件,您将看到模型文件中定义的每个事务都有一个函数。

让我们检查到目前为止我们一直在玩的事务之一——InitialApplication事务。注意函数的起始方式:

/**
  * Create the LOC asset
  * @param {org.example.loc.InitialApplication} initalAppliation - the InitialApplication transaction
  * @transaction
  */
 async function initialApplication(application) {

注释和程序代码的第一行有效地说明了以下函数实现了InitialApplication事务,该事务接受org.example.loc.InitialApplication类型,并将其赋给了本地作用域的application变量。简而言之,它将程序逻辑与模型文件中看到的事务定义连接起来。

代码的第一行是以下内容:

const letter = factory.newResource(namespace, 'LetterOfCredit', application.letterId);

factory.newResource()org.example.loc命名空间中使用由函数调用者在输入application.letterId事务变量中提供的标识符创建了一个新的本地LetterOfCredit。此语句将此函数的结果赋给了本地letter变量。

重要的是要理解,此语句并没有在业务网络中创建一封信件;factory.newResource()只是创建了一个正确形状的 JavaScript 对象,现在可以由以下后续逻辑来操作,并且在使用调用者提供的输入正确形成之后(例如,由 Alice 使用的申请),它可以被添加到业务网络中!

注意applicantbeneficiary是如何赋值的:

letter.applicant = factory.newRelationship(namespace, 'Customer', application.applicant.getIdentifier());
letter.beneficiary = factory.newRelationship(namespace, 'Customer', application.beneficiary.getIdentifier());

交易确保 Alice 和 Bob 的标识符正确地放置在信函中。在我们的网络中,application.applicant.getIdentifier()会解析为resource:org.example.loc.Customer#aliceresource:org.example.loc.Customer#bob。交易逻辑有条不紊地使用提供的输入和已经存储在业务网络中的信息构建信用证。

接下来,请注意issuingBankexportingBank如何通过参与者导航到其银行。程序逻辑会浏览参与者和资产定义中的引用来做到这一点:

letter.issuingBank = factory.newRelationship(namespace, 'Bank', application.applicant.bank.getIdentifier());
letter.exportingBank = factory.newRelationship(namespace, 'Bank', application.beneficiary.bank.getIdentifier());

我们可以看到这些声明中,交易必须使用在模型中定义的结构。它可以添加任何专有的业务逻辑,但必须符合此结构。检查每行分配给letter的内容,看看您是否能理解在这些术语中发生了什么。需要花点时间来适应,但理解这一点非常重要 - 交易通过这种逻辑将业务网络从一个状态转变为另一个状态。

注意信函分配的最后一条声明:

letter.status = 'AWAITING_APPROVAL';

看看枚举类型如何被用于设置信函的初始状态。

在函数中下一个非常重要的声明是以下内容:

 await assetRegistry.add(letter);

现在这封信已经被添加到了业务网络中!此时,我们在业务网络中为信用证创建了一个新的应用程序。我们在本地存储中创建的信函已被发送到网络,并且现在是一个指向网络中参与者和资产的实时资产。

最后,我们发出一个事件来表示交易已经发生:

const applicationEvent = factory.newEvent(namespace, 'InitialApplicationEvent');
applicationEvent.loc = letter;
emit(applicationEvent);

就像处理信函一样,我们创建了一个正确形状的本地事件 - 一个InitialApplicationEvent,完善其细节,然后emit()它。通过检查不同的交易及其逻辑,您将对每个交易的精确处理变得更加熟悉 - 这个努力将给您带来丰厚的回报。

创建业务网络 API

在本章的最后部分,我们将展示您的应用程序如何使用 API 与业务网络中的交易功能进行交互。示例应用程序和 Playground 都使用 API 与业务网络进行交互。

实际上,您可以看到从服务消费者的角度来看,无论是 Alice、Bob、Matias 还是 Ella 都不知道区块链 - 他们仅仅与一些用户界面交互,最终导致这些交易功能(或类似的功能)被执行,以根据这些交易处理函数中编码的业务逻辑来操作业务网络。

正是这些用户界面和应用程序使用 API 与业务网络进行交互。如果您对 API 是新手,您可以在这里阅读相关信息。尽管更加技术上准确,但很少有人使用术语Web API - 它就是API

让我们来看看我们业务网络的 API!如果您选择演示的最终选项卡,您将看到以下页面:

这是 Hyperledger Composer REST 服务器。它是一个服务器,正在公开我们业务网络中的 API。这些 API 使用标准 SWAGGER 格式进行描述。

SWAGGER API 定义

SWAGGER 是一种描述 API 的开放标准 – swagger.io/specification/ 这些 API 是由 Hyperledger Composer 生成的,使用与模型中定义的相同词汇来描述为此业务网络定义的参与者、应用程序和交易!这意味着 SWAGGER API 对业务用户和技术用户都具有明显的意义。

对于业务网络中的每种参与者、资产和交易类型,都有相应的 API。

使用 SWAGGER 查询网络

选择其中一个 API LetterOfCredit

注意此 API 的 GETPOST 动词。大多数现代 API 使用 REST 和 JSON 进行定义,这就是您在此处看到的。练习展开和折叠视图,以查看所有不同的选项。

当您满意时,选择 InitialApplication GET

就像使用 Playground 一样,您可以使用相同的 API 与业务网络交互。作为程序员,您应该对这个视图感到满意,尽管它要技术得多。

我们选择的 API 允许程序查询(GET)业务网络中的所有信件。如果您选择“试一试**!**”,您将看到以下响应:

这些详细信息向您展示了发出的确切 API。这是一个在

http://localhost:3000/api/LetterOfCredit URL,响应体显示返回的数据。您应该能够看到它的结构与 Playground 数据非常相似,如果您滚动浏览响应,您将看到网络中的两封信。

从命令行测试网络

您还可以使用 curl 命令从终端与网络交互,并为您显示语法:

curl -X GET --header 'Accept: application/json' 'http://localhost:3000/api/LetterOfCredit'

在终端中尝试此操作,您将在命令行上看到数据:

它比 Playground 或 SWAGGER 视图要不那么美观,但如果您是程序员,您知道这有多强大!想想这如何帮助自动化测试,例如。

使用 SWAGGER 创建新信件

我们还可以从 SWAGGER 视图为信用证创建一个新的应用程序。选择 InitialApplication API。

我们将使用 POST 动词为 Alice 创建另一个申请:

value 框中,粘贴以下数据:

{
  "$class": "org.example.loc.InitialApplication",
  "letterId": "LPLAYGROUND2",
  "applicant": "resource:org.example.loc.Customer#alice",
  "beneficiary": "resource:org.example.loc.Customer#bob",
  "rules": [
   {
    "$class": "org.example.loc.Rule",
    "ruleId": "rule1",
    "ruleText": "This is a test rule."
   }
  ],
  "productDetails": {
   "$class": "org.example.loc.ProductDetails",
   "productType": "Mouse Mat",
   "quantity": 40000,
   "pricePerUnit": 5
  }
 }

你能看出这个应用的作用吗?你能看到 Alice 想要向 Bob 以每件5美元的价格购买40000块鼠标垫的信件申请吗?

如果你点击“尝试一下!”按钮,将会创建一个新的信件!你现在可以使用 SWAGGER 控制台、应用程序或 Playground 查看这封新的信件。让我们试一下:

这是使用 SWAGGER 的视图:

这是使用 Playground 的视图:

这是使用该应用的视图(Matias 的视图):

网络卡和钱包

最后,在结束本章之前,我们将把添加到这个业务网络,这样你就可以提交交易了!为了做到这一点,我们将返回到最初允许我们连接到网络的业务网络卡钱包。请记住,所有应用程序,包括 Playground,都有一个包含业务网络卡的钱包,可以用来连接不同的网络。当应用程序使用特定卡来连接网络时,它被标识为网络中特定的参与者实例。

  1. 让我们创建一个新的参与者!在测试标签页上,选择客户参与者:

  1. 你将看到 Alice 和 Bob 的参与者信息。点击“创建新参与者”:

这个页面将让你发出 API 并创建一个新的参与者。我们为一个名为Anthony的新参与者输入了以下详细信息,他在 BlockIT 工作:

{
   "$class": "org.example.loc.Customer",
   "companyName": "BlockIT",
   "personId": "Customer003",
   "name": "Anthony",
   "bank": "resource:org.example.loc.Bank#BOD"
}

注意他的标识符和对 Dinero 银行的引用。点击“创建新的”,注意参与者注册表已更新:

我们在网络中创建了一个新的参与者。(也可以自己输入详细信息,只要确保你的参与者有有效的数据,特别是参考现有的银行。)

点击ID 注册表下的管理员。现在你将看到与 Playground 相关的身份列表。

而 Alice 和 Bob 的数字证书是私有的,只在他们的应用程序中可见,这里我们可以看到当前 Playground 用户(业务网络管理员)相关的身份信息:

点击“发出新的 ID”:

输入ID003为 ID 名称,并与我们创建的新参与者关联,org.example.loc.Customer#Customer003,然后点击“创建新的”:

为业务网络卡命名,并点击“添加到钱包”。

你将看到 ID 列表已更新,关联了ID003,与Customer003相关:

点击管理员标签下的我的业务网络用户,返回到 Composer Playground 的初始页面:

我们可以看到 Playgrond 钱包现在包含了一个新的业务网络卡,允许您连接到我们的网络。点击“现在连接”以使用 Cusotmer003Card。您现在作为 Customer003 而不是 Admin 连接到了网络。

访问控制列表

所有应用程序,包括 Composer Playground,都使用来自其钱包的业务网络卡(本地文件系统上的文件)连接到网络。该卡包含网络的 IP 地址、参与者的名称和他们的 X.509 公钥。网络使用这些信息来确保他们只能对网络中的某些资源执行特定操作的权限。例如,只有特定的银行员工才能授权信用证。

通过检查网络的访问控制列表(ACL),您可以看到这些权限是如何为业务网络定义的。在“定义”选项卡上选择“访问控制”:

滚动查看列表,查看不同用户对网络中不同资源拥有的权限。这些规则可能与类型或实例相关,尽管前者更常见。花一点时间研究此文件中的 ACL 规则。

摘要

您已经学会了如何使用超级账本技术建立真实的业务网络。您知道如何作为用户、设计师和应用程序开发人员与业务网络进行交互。您知道如何定义参与者、资产、交易和事件,并如何在代码中实现它们的创建。您知道如何将这些暴露为 API,以便外部应用程序可以使用它们!您可以通过查阅产品文档来了解更多关于超级账本 Composer 和超级账本 Fabric 的信息。拥有这些信息以及本章的知识,您已经可以开始构建自己的业务网络了!

现在让我们转向如何在区块链网络中管理开发生命周期 - 如何在区块链网络中实现敏捷性。我们将研究帮助我们设置和管理日常操作以开发区块链软件的过程和工具。

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

此时,如果一切顺利,您应该拥有一个完全功能的去中心化应用程序,其中智能合约正在 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 来说,大小写很重要:[email protected] 和 [email protected]不是相同的电子邮件。

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

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) <[email protected]>
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 解决方案需要支持治理过程。

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

第十一章:Hyperledger Fabric 安全性

Hyperledger Fabric 是一个模块化的区块链系统。它经过设计允许一组已知的参与者在区块链网络中参与并执行操作(所谓的许可区块链)。由于其模块化的特性,它可以在许多不同的配置中部署。Hyperledger Fabric 的不同部署配置对网络操作者及其用户具有不同的安全影响。

在其核心,Hyperledger Fabric 是一个公钥基础设施PKI)系统,因此它继承了与这类系统相关的安全性(和复杂性)。在编写本书时,Hyperledger Fabric v1.1 已发布。

设计和实现区块链网络的安全方面已在先前的应用章节中讨论过,我们打算在这里更广泛并更深入地了解 Hyperledger Fabric 的安全特性。

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

  • 影响安全性的设计目标

  • Hyperledger Fabric 架构简述

  • 网络引导和治理 - 安全的第一步

  • 强身份 - Hyperledger Fabric 网络安全的关键

  • 链代码安全性

  • 常见安全威胁以及 Hyperledger Fabric 如何减轻这些威胁

  • Hyperledger Fabric 和量子计算

  • 常规数据保护法规(GDPR)考虑

影响安全性的 Hyperledger Fabric 设计目标

要了解 Hyperledger Fabric 的安全性,重要的是阐明影响安全性的关键设计目标:

  • 现有成员应确定如何在网络中添加新成员:网络中的现有实体必须就是否接收新实体的加入达成一致意见。这一原则是创建许可区块链的基础。与其允许任何实体下载软件并连接到网络,网络成员必须就接受新成员的政策达成一致意见(例如,通过多数投票),然后由 Hyperledger Fabric 强制执行。在成功投票后,新成员的数字凭证可以被加入到现有网络中。

  • 现有成员应确定如何更新配置/智能合约:与第一条类似,网络配置的任何更改或部署或实例化智能合约都必须得到网络成员的同意。总结起来,第一和第二点赋予了 Hyperledger Fabric 执行许可区块链的能力。

  • 账本及其相关的智能合约(链码)可能会被范围化以满足更广泛的隐私和保密要求:在公共区块链网络中,所有节点都拥有区块链账本的副本并执行智能合约。为了保持机密性和范围化,需要创建存储与其交易相关的账本的一组节点(在 Hyperledger Fabric 中为通道和通道私有数据)。更新这种账本的智能合约(在 Hyperledger Fabric 中为链码)将范围化到这样一个组的成员。

只有参与通道的成员才需要确定如何更新该通道的配置。

  • 智能合约可以用通用语言编写:Hyperledger Fabric 的主要设计目标之一是允许智能合约用通用语言编写,如 Go 和 JavaScript。显然,如果在执行前没有治理和流程来验证和部署智能合约,允许通用语言编写智能合约会暴露系统于各种安全问题。即便如此,用通用语言编写的智能合约也应该被合理地隔离,以限制它们可能无意中造成的危害。

  • 必须确保事务完整性:事务是智能合约的执行。事务必须以一种方式创建和存储,以防止其他节点篡改它们,或者使其易于检测到任何篡改。通常,确保事务完整性需要使用加密原语。

  • 应利用行业标准:系统应利用行业标准来断言数字身份(例如,X.509 证书),以及节点之间的通信(例如,TLS 和 gRPC)。

  • 共识与交易执行和验证分离:现有的区块链网络将交易执行和验证与区块链网络中的节点达成共识紧密结合在一起。这种紧密耦合使得很难实现共识算法的可插拔性。

  • 无处不可插拔:系统应具有模块化设计,并且每个模块都应通过标准接口可插拔。能够插入特定于网络的模块使得 Hyperledger Fabric 具有在各种环境中使用的灵活性。然而,这种可插拔性也意味着基于 Hyperledger Fabric 的两个不同的区块链网络实例可能具有不同的安全属性。

要了解这些原则如何影响 Hyperledger Fabric 的安全性,我们将简要解释 Hyperledger Fabric 的架构。详细的架构请参考前面的章节。

Hyperledger Fabric 架构

Hyperledger Fabric 架构如下所示:

Hyperledger Fabric 架构

Fabric CA 或成员服务提供商

成员服务提供商MSP)负责为组织的节点和用户创建数字身份。节点的身份必须在现有网络中进行配置,以便新实体可以参与通道。

Fabric CA 是 MSP 的一种实现,提供了从网络成员注册用户并为他们颁发数字身份(X.509 证书)的机制。Fabric CA 通常在 Docker 容器中运行。每个 Fabric CA 都配置有后端数据库(默认为 SQLite,其他选项包括 PostgreSQL 或 MySQL),用于存储注册的身份,以及它们的 X.509 证书。Fabric CA 不存储用户的私钥。

节点

一个节点是参与 Hyperledger Fabric 网络的实体。它的身份是根据其对应的成员服务提供商确定的。节点负责部署和实例化链代码,更新账本,与其他节点交互以共享与交易相关的私有数据,并与订购服务以及其运行的智能合约(在前面的截图中称为链代码)进行交互。类似于 Fabric CA,一个节点通常也在 Docker 容器中运行。

智能合约或链代码

智能合约SC)是应用逻辑,以高级语言(如 Go 或 JavaScript)编写;成功执行时,它读取或写入最终提交到账本的数据。智能合约没有直接访问账本的权限。一个节点可以部署零个或多个作为 Docker 容器运行的智能合约。一个节点也可以部署多个版本的智能合约。

账本

每个节点维护着一个数字账本,其中包含节点接收到的所有已提交交易的记录。账本中的条目以键/值对的形式存储。对同一键的更新将用新值替换键的当前值。当然,旧值将保留在账本中。为了有效查询键的最新值,一个节点可以将每个键的最新值存储在诸如CouchDB之类的数据库中。这个数据库在超级账本中被称为世界状态。

请注意,一个节点只会从它参与的通道接收要提交到它账本的区块。

一个节点可以是零个或多个通道的一部分 —— 在前面展示超级账本架构的图表中没有显示通道。

私有数据

随着 Hyperledger Fabric v1.1,节点可以选择通过链私有数据实验性功能(jira.hyperledger.org/browse/FAB-1151)与通道中的一部分节点选择性共享私有数据。账本上的区块只包含此类数据的哈希,而私有数据存储在账本之外的私有状态数据库中。

订购服务

订购服务负责接收来自对等方的执行交易,在将它们组合成区块,并将其广播给相同通道上的其他对等方。接收交易区块的对等方在提交到总账簿之前会对其进行验证。订购服务的责任是不将一个通道上的区块混入到另一个通道上。

在 Hyperledger Fabric 1.0 版本中,对等方会将交易(密钥和关联值,以及读/写集)发送到订购服务。因此,订购服务对于与交易相关的所有数据都是可见的,这在保密方面有着影响。在 Hyperledger Fabric 1.1 版本中,客户端可以将交易数据的哈希值(输入和读/写集)发送到订购服务,同时将与交易相关的数据直接传输给相关的对等方。

目前,订购服务是使用 Kafka 实现的,并且是崩溃容错CFT)的,但不是拜占庭错误容忍BFT)。但这是一个时间点的说法,因为 HyperLedger 据称是可插拔的,包括共识服务。可插拔性意味着未来可能会提供其他共识模型。

尽管在描述 Hyperledger Fabric 架构的图表中没有显示,但对等方,订购者和 fabric 使用可插拔的加密服务提供者,允许他们插入新的加密算法以及硬件安全模块(HSMs)(en.wikipedia.org/wiki/Hardware_security_module)来管理加密密钥。

网络引导和治理 - 通往安全的第一步

当组织决定使用 Hyperledger Fabric 组建许可的私有区块链网络时,他们需要考虑几个治理方面,这将最终决定网络的整体安全状况。这些治理方面包括但不限于以下内容:

  • 网络应如何引导启动和成员验证以创建网络? 网络引导是创建区块链网络的第一步。不同的实体可以一起创建一个网络。这些实体可以进行场外通信,就第一批成员达成一致,并制定治理政策,接下来将进行讨论。

  • 新实体加入网络(或通道)的流程是什么? 确定网络中新成员的准入政策是至关重要的,并由网络的业务需求来规定。

  • 谁可以在网络上的对等方上部署和升级链码? 确定一个过程是重要的,以防止恶意或有 bug 的链码被安装在一个或多个对等方上(参见第七章业务网络示例)。

  • 将在区块链上存储什么数据模型? 成员必须就将存储在区块链中的共同数据模型达成一致意见;否则,区块链对其成员将毫无用处。数据模型应设计得不会违反任何合规法规,例如通用数据保护条例GDPR)(gdpr-info.eu/)。

创建网络

当实体决定创建网络时,他们必须决定以下事项:

  • 谁将运行排序服务

  • 网络中将有多少不同的排序服务实例

排序服务的角色非常关键,因为根据配置,它可以看到流经它的所有频道的交易哈希或交易数据。因此,决定组建网络的实体可以选择信任其中一个实体充当排序服务;他们还可以决定信任中立的第三方来运行排序服务。

排序服务可以查看其提供服务的所有频道中的所有交易(哈希或键值对)。因此,如果需要向排序服务隐藏交易数据,则在对等方之间直接交换数据时,应仅向排序服务发送交易中读/写集的哈希。

一旦为网络建立了排序服务,就必须将创始成员的对等方的数字身份配置到其中。这通常是通过在排序服务创世区块中配置对等方的数字证书来完成的。对等方还必须配置排序服务的数字身份。

添加新成员

在创建网络或频道时的创始成员必须还要定义新成员如何加入网络或频道的政策。默认情况下,此政策通常是由多数人选择的(即两个中的两个,三个中的两个,四个中的三个等)。成员可以决定网络中任何其他接纳新成员的政策。对于接纳新成员的政策的任何更改通常将通过商业协议决定。一旦达成协议,就可以根据当前政策更新频道配置,以反映接纳新成员的新政策。

创世区块的创建以及用于更新配置的后续交易是特权操作,必须在确认之前经过对等方管理员的批准。

部署和更新链码

一旦成员决定参与通道,他们可以选择部署和实例化链码(又称智能合约)。链码定义了如何更新或读取通道范围内的键/值对。链码可以定义其认可策略——即,它可以要求网络中一些或所有对等方的数字签名。由于 Hyperledger Fabric 的权限性质,需要对等方的数字签名(认可)的链码必须安装和实例化在对等方上。有关部署链码的更多详细信息,请参见第五章公开网络资产和交易第七章业务网络示例

在通道上部署链码之前,预期网络成员将希望审核链码,以确保其符合其政策。此过程可以正式化为链码治理,要求所有相关成员对其节点实例化的链码进行强制审核。

建立一个在同行上部署链码的流程,包括手动审核和验证链码作者数字签名。

数据模型

实体必须就将存储在区块链中的数据模型达成一致意见,这反过来由链码确定。网络或通道部署链码的创始成员将确定存储在通道中的键/值对。此外,成员将决定哪些数据与其他成员共享,哪些数据他们将保留私有给自己或一部分成员。数据模型应设计得对成员希望完成的业务功能有用,合理地具有未来的保障,并且不会无意中泄露信息。请记住,通道中的所有参与对等方都存储了提交的交易(及其键/值对)。

建立一个定义将存储在通道中的数据模型的流程。

前述步骤可总结如下:

  1. 确定谁将运行订购服务。

  2. 配置订购服务中创始成员的数字身份。

  3. 创建通道并确定接纳新成员的通道政策。

  4. 定义编写、分发、部署和实例化链码的治理。

  5. 建立数据模型。

强身份——Hyperledger Fabric 网络安全的关键。

强身份是 Hyperledger Fabric 安全的核心。创建、管理和撤销这些身份对于基于 Hyperledger Fabric 的部署的运行安全至关重要。这些身份由一个 MSP 发行。如前面的 Hyperledger Fabric 架构图所示,一个逻辑 MSP 通常与一个对等体关联。MSP 可以发行任何适当的加密签名的身份。Hyperledger Fabric 配备了一个默认的 MSP(Fabric CA),该 MSP 为经过身份验证的实体发行 X.509 证书。

引导 Fabric CA

Fabric CA 可以配置一个 LDAP 服务器或以独立模式运行。当以独立模式运行时,必须配置一个引导身份,该身份存储在 Fabric CA 的后端数据库中。默认情况下,使用 SQLite 数据库,但对于生产用途,可以配置 PostgreSQL 或 MySQL 数据库。如果使用独立服务器,则 Fabric CA 服务器与其数据库之间的连接通常通过 TLS 进行。

在本章的其余部分,我们将在没有 LDAP 服务器的情况下运行时将引导实体称为 ca-admin。在没有 LDAP 服务器的情况下运行时,必须在 Fabric CA 的引导时提供 ca-admin 及其密码。

为了让 ca-admin 与服务器交互,必须向 Fabric CA 服务器提交一个证书签名请求CSR)以获取 X.509 证书。这个过程称为注册身份,或简称注册。拥有 X.509 证书后,ca-admin 就可以添加其他用户,我们将在下面解释。

将管理员用户的密码保存在安全的地方,因为这是您组织的 root 用户。将其视为您对待 root Linux 用户的密码一样安全。使用它创建具有适当权限的新用户,但除了在安全漏洞的情况下,永远不要使用此用户进行任何其他操作,在这种情况下,此用户可用于撤销所有注册实体的证书。

Fabric CA 在系统中提供了两个关键操作,即注册和登记。我们将在下面解释这些操作。

注册

注册操作将指定的标识符添加到 Fabric CA 中的新实体。注册操作不为用户创建 X.509 证书;这在注册操作中发生。由 Fabric CA 的管理员定义将新用户添加到网络的政策和程序。

在注册用户时,有一些重要的注意事项:

  • 如果策略是注册电子邮件地址,则在随后的注册过程中,用户的电子邮件地址将被编码在证书中。在 Hyperledger Fabric 中,发起交易的用户的证书与提交的交易一起存储在账本中。任何人都可以解码证书并确定电子邮件地址。

仔细确定如何在 Fabric CA 中注册新实体,因为当这些实体发出交易时,它们的数字证书将最终出现在账本上。

  • 另一个重要考虑点是允许该用户的注册次数。每个注册将导致向用户发放新证书。在 Hyperledger Fabric 中,正在注册的新用户可以被注册有限次数,或者可以具有无限次数的注册。通常,正在注册的新实体不应该配置为具有无限次数的注册。

对于新用户,最好将最大注册数设置为 1。这个设置确保实体和其数字证书之间存在一对一的对应关系,从而使实体吊销管理更容易。

  • 使用 Hyperledger Fabric 1.1,现在可以在注册时为实体定义属性。然后,这些属性将被编码到实体的 X.509 证书中。

在独立模式下使用时,在成功注册时,Fabric CA 将创建一个唯一密码(如果在注册过程中未提供)。然后,ca-admin 可以将此密码传递给被注册的实体,后者将使用它创建 CSR 并通过注册操作获取证书。

默认 Fabric 角色

在 Fabric CA 中注册实体时,实体应该具有一组角色。Fabric CA 配置了以下默认角色:

hf.Registrar.Roles = client, user, peer, validator, auditor

Fabric CA 可以注册任何具有以下角色之一的实体:

hf.Registrar.DelegateRoles = client, user, validator, auditor

Fabric CA 可以吊销角色:

hf.Revoker = true

Fabric CA 还可以注册中间 CA:

hf.IntermediateCA

在 Fabric CA 中注册身份,实体必须具有 hf.Registrar。角色被赋予一个逗号分隔的值列表,其中一个值等于正在注册的身份类型。

其次,调用者身份的关联必须等于或是被注册身份的关联的前缀。例如,具有 a.b 关联的调用者可以注册具有 a.b.c 关联的身份,但不能注册具有 a.c 关联的身份。

注册

拥有 ID 和密钥的实体随后可以使用 Fabric CA 注册自己。为此,它生成公钥/私钥对,创建 CSR,并将其与 Authorization 标头中注册的 ID 和密钥一起发送到 Fabric CA。验证成功后,服务器将向被注册的实体返回 X.509 证书。发送注册请求的实体负责管理私钥。这些私钥应该以安全的方式存储(如硬件安全模块)。

证书签名请求中允许哪些加密协议?

CSR 可以定制为生成支持 椭圆曲线数字签名算法 (ECDSA) 的 X.509 证书和密钥。支持以下密钥大小和算法:

大小 ASN1 OID 签名算法
256 prime256v1 ecdsa-with-SHA256
384 secp384r1 ecdsa-with-SHA384
521 secp521r1 ecdsa-with-SHA512

吊销身份

由于 Hyperledger Fabric 是一个 PKI 系统,必须从系统中删除的身份必须明确撤销。这是通过标准证书吊销列表CRLs)完成的。必须将 CRLs 同步到所有组织中,以确保每个人都能检测到被吊销的证书。向其他对等方分发 CRLs 需要使用带外机制。

Fabric CA 中管理用户的实际考虑

通常,组织有自己的身份(LDAP)服务器来管理其员工。一个组织可以选择参与一个或多个 Hyperledger Fabric 网络,但是只有其员工的子集可能被注册到每个网络。每个网络的 Fabric CA 管理员可以选择在每个网络中注册一部分员工。

由于员工必须生成和管理私钥才能成功参与 Hyperledger Fabric 网络,因此管理私钥及其相应的数字证书的责任在于组织的员工。管理私钥和数字证书并不简单,这可能会给员工带来不必要的负担,并且可能会导致员工无意中暴露密钥。由于员工需要记住其组织发放的凭据(例如用户名和密码)以登录组织系统,组织可以选择代表参与一个或多个 Hyperledger Fabric 网络的员工管理私钥和证书。根据行业的不同,私钥可能存储在硬件安全模块中,这将使得篡改密钥变得不可行。硬件安全模块的精确配置超出了本章的范围。

链码安全性

在 Fabric 中,智能合约,也称为链码,可以用 Go 或 JavaScript 编写。链码必须安装在对等方上,然后明确启动。当启动时,每个代码都在单独的 Docker 容器中运行。以前版本的链码也在单独的 Docker 容器中运行。

运行链码的 Docker 容器可以访问虚拟网络以及整个网络堆栈。如果在将链码安装到对等方之前不仔细审查链码,并将该链码的网络访问隔离开来,则可能会导致恶意或配置错误的节点探测或连接到相同虚拟网络上附加的对等方。

运营商可以配置策略来禁用链码 Docker 容器上的所有出站或入站网络流量,除了白名单节点。

如何与其他背书对等方共享链码?

组织必须建立一个流程,与参与 Hyperledger Fabric 网络的其他组织分享链码。由于链码必须安装在所有背书对等方上,因此在与其他对等方共享时需要通过加密机制确保链码的完整性。更多关于分享链码方法的详细信息,请参阅第八章区块链网络中的灵活性。这个问题也在 Nettitude 对 Hyperledger Fabric 进行的安全评估中得到了强调wiki.hyperledger.org/_media/security/technical_report_linux_foundation_fabric_august_2017_v1.1.pdf

谁可以安装链码?

要在对等方上安装链码,实体的证书必须安装在对等方的节点上(存储在本地 MSP 中)。由于安装链码是一项权限非常高的操作,必须小心,只有具有管理能力的实体才能执行此操作。

链码加密

实体可以选择在调用链码时使用 AES 加密密钥来加密键/值对(github.com/hyperledger/fabric/tree/master/examples/chaincode/go/enccc_example)。加密密钥传递给链码,然后在发送提案之前加密值。需要解密值的实体(例如,为了支持交易)必须拥有密钥。预期这些加密密钥将以点对点的方式与其他对等方共享。

基于属性的访问控制

正如你可能还记得第四章中所提到的 使用 Golang 设计数据和事务模型,Hyperledger 1.1 新增的功能之一是基于属性的访问控制。在注册实体时,可以为实体指定属性,然后这些属性将在注册时添加到 X.509 证书中。属性的示例包括由参与网络的组织约定的角色名称,例如“审计员”。当执行链码时,它可以在调用或查询操作之前检查身份是否具有某些属性。在简单级别上,这允许应用级别的属性通过 X.509 证书传递到链码中。

属性访问控制的优缺点

在证书中编码属性具有其自身一套优缺点。一方面,与身份相关的所有信息都编码在证书中,因此可以基于属性做出决策。另一方面,如果必须更新属性,例如,用户转移到不同部门,就必须吊销现有证书,并发行一个具有新属性集的新证书。

Hyperledger Fabric 如何应对常见威胁

Hyperledger Fabric 针对一些最常见的安全威胁提供了保护,并假定了一个共享责任模型来解决其他威胁。在下表中,我们将总结最常见的安全威胁,以及 Hyperledger Fabric 是否解决了这些威胁以及如何解决,或者这是否是节点/网络操作员的责任来解决这些威胁:

威胁 描述 Hyperledger Fabric 网络/节点操作员
欺骗 使用令牌或其他凭证来假冒授权用户,或者 compromise 用户的私钥。 Fabric 证书颁发机构为其成员生成 X.509 证书。 管理证书吊销列表的分发,以确保被吊销的成员不能再访问系统。
篡改 修改信息(例如,数据库中的条目)。 使用密码措施(SHA256,ECDSA)使篡改变得不可行。 源自 Fabric。
否认 实体无法否认谁做了什么。 使用数字签名跟踪谁做了什么。 源自 Fabric。
重放攻击 重放交易以破坏分类帐。 Hyperledger Fabric 使用读/写集来验证交易。重放交易将由于无效的读取集而失败。 源自 Fabric。
信息泄露 通过有意的违规或意外曝光暴露的数据。 Hyperledger Fabric 支持使用 TLSv1.2 进行在传输时加密。它不会加密静态数据(操作员的责任)。系统中所有对等体及其交易的信息都会暴露给排序服务。 通过遵循信息安全最佳实践以及静态数据加密,操作员有责任防止信息泄露。
拒绝服务 使合法用户访问系统变得困难。 这是操作员的责任。 防止系统遭受拒绝服务攻击是操作员的责任。
特权提升 获得应用程序的高级访问权限。 已经发行的身份无法升级其访问权限(例如,创建身份)而不经过手动审核。 Hyperledger Fabric 在 Docker 容器中运行链代码。限制访问并使用适当的限制运行链代码容器是网络/节点操作员的责任。
勒索软件 使用加密或其他手段阻止对文件系统上数据的访问。 运营者的责任。 运营者有责任确保勒索软件无法阻止节点账本的访问。

Hyperledger Fabric 中的交易隐私

Hyperledger Fabric 的主要设计考虑之一是提供交易的隐私和保密性。Hyperledger Fabric 提供了一些机制来实现这些目标。

通道

Hyperledger Fabric 节点如果只想与网络中的一部分节点共享数据,则可以通过通道实现。在这些情况下,只有参与通道的同行者才能存储交易数据;不参与通道的同行者无法看到交易数据,因此也无法存储交易数据。然而,此数据暴露给了排序服务。一个健壮的通道设计将解决参与者之间的隔离、数据隐私和保密性以及具有健壮审计能力的受控/有权限访问。

私有数据

通道中的同行者可以选择确定他们将与哪些其他同行者共享他们的数据。私有交易数据在同行者之间点对点传递,而仅将交易数据的哈希广播给排序服务和未共享此数据的同行者。

加密交易数据

同行者还可以选择在发送交易进行认可之前对交易数据进行加密。然而,对于认可交易的同行者来说查看数据可能是必要的。必须使用一种带外机制在这些同行者之间交换加密密钥。

Hyperledger Fabric 和量子计算

Hyperledger Fabric 使用椭圆曲线密码学对交易进行数字签名。椭圆曲线密码学依赖于数学技术,可以通过量子计算加速。然而,Hyperledger Fabric 提供了可插拔的密码提供者,允许用其他算法替换这些数字签名算法。此外,根据 NIST 信息技术实验室主任的说法,量子计算对区块链系统安全的影响至少还有 15 到 30 年才会成为现实。

通用数据保护条例 (GDPR) 考虑因素

通用数据保护条例GDPR)是一项欧盟法律,规定了个人数据如何被获取、处理和最终从计算系统中删除。GDPR 中对个人数据的定义非常广泛——例如姓名、电子邮件地址和 IP 地址。

区块链本质上创建了数据的不可变、永久和复制记录。基于 Hyperledger Fabric 的区块链网络显然将涵盖这三个特性。因此,在不可删除或修改的区块链网络上存储个人数据可能会从 GDPR 的角度带来挑战。同样重要的是要知道个人数据与谁共享。

Hyperledger Fabric 的通道和通道私有数据特性提供了一种确定与之共享数据的实体的机制。在通道私有数据的情况下,数据永远不会存储在区块链上,但其加密哈希存储在链上。通过治理流程,节点可以确定与之共享此数据的其他节点。Hyperledger Fabric 中的通道私有数据特性可能提供了一种将个人数据存储在链外,并确定与之共享此数据的对象的机制,同时通过存储在区块链中的加密哈希维护此数据的完整性。

Hyperledger Fabric 还会在数字账本中存储创建交易的实体的 X.509 证书。这些 X.509 证书可能包含个人数据。从版本 1.1 开始,Hyperledger Fabric 提供了一种基于零知识证明的身份证明机制,同时隐藏属性的实际值。这些基于零知识证明的凭证随后存储在账本中,取代传统的 X.509 证书,并且有助于达到 GDPR 的合规性。

摘要

在本章中,我们首先讨论了与安全相关的 Hyperledger Fabric 的设计目标。所有描述的一系列点都被认为是考虑到 Fabric 安全性。我们简要研究了 Hyperledger Fabric 安全性,并了解了强身份是 Fabric 安全的核心。我们还简要讨论了链码安全性。

Hyperledger 本身擅长处理威胁。我们深入了解了常见的 Hyperledger 安全威胁以及 Fabric 如何应对它们。

我们还简要讨论了量子计算对 Hyperledger Fabric 的影响。

第十二章:区块链技术介绍

本章节将概述区块链及其关键概念,如加密学、哈希算法、分布式账本、交易、区块、工作证明、挖矿和共识。我们详细介绍了区块链技术的鼻祖比特币。我们通过指出比特币的一些局限性以及以太坊如何解决这些问题,简要介绍了以太坊。虽然比特币和以太坊是公共区块链的示例,但 IBM 的超级账本被用作企业区块链的示例。在本章的最后,我们提到了区块链的演变:基于它们的用例,包括区块链 1.0、2.0、3.0 及以后版本。具体来说,我们将涵盖以下区块链主题:

  • 区块链的家谱类比

  • 比特币共识机制

  • 对超级账本的简要讨论

  • 区块链的演变

家谱类比

作者之一最近参加了北京的一次中国大学校友聚会,在那里,区块链成为了一个热门讨论话题。一位备受尊敬的校友和学者,杨教授,他曾撰写过关于密码学和公共数据保障的书籍,用家谱来描述区块链。这是一个深思熟虑的类比,因为它直观、易于理解地解释了区块链。这里借用这个类比来说明技术背后的基本思想。

在中国的古老时代,每个姓氏家族都有一个习俗,即保存该家族的族谱副本(具有相同姓氏的人)。当家族成员因婚姻、生子或收养而发生变化时,新成员的姓名会出现在每个副本中。然而,新成员必须在姓名被添加之前得到家族的接受。由于各种原因,有时候结婚未得到大多数家族的认可。在这种情况下,新成员的姓名就不会被录入家谱中。换句话说,当一个新成员加入家族时,消息会传达给其他家族。如果家族就接受新成员达成共识,每个家族都会更新他们的家谱副本以反映这一变化。另一方面,如果家族决定不接受新成员,姓名就不会被添加。家谱可以用于验证目的。例如,如果一个陌生人声称是家族的成员,或者两个拥有相同姓氏的人想知道他们是否有共同的祖先,有了家谱,就很容易验证。结果会被接受,因为家谱被认为是可靠的,这要归功于上述共识和分散记录,除非大多数家族同意,否则很难操纵。

区块链与家谱有许多相似之处,总结如下:

  • 就像一个由许多相关家庭组成的家族一样,区块链网络由节点组成。每个节点就像一个家庭。

  • 就像每个家庭都保存一份家谱副本一样,区块链的每个节点都保留着从一开始就发生在链上的所有交易的副本。所有交易的集合构成了一个分类帐。这使得区块链成为了一个分散的数据存储库。

  • 一个家谱以一个家族的共同祖先开始,名字与直系亲属之间的关系,如父母和子女,通过一条线连接起来。同样,分类帐由块组成。每个块包含一个或多个交易,取决于区块链的类型。(正如您将在后面看到的,比特币或以太坊上的区块包含多个交易,而 R3 的 Corda 使用只有一个交易的块)。交易就像名字,而块类似于包含夫妇名字的看不见的盒子。根祖先的等价物称为创世块,它是区块链的第一个块。类似于连接父母和子女的线,一个哈希,稍后将更详细地解释,从当前块指向其祖先块。

  • 就像为将新的名字添加到家谱的共识机制一样,比特币区块链使用一种称为工作证明的机制来决定是否可以将一个块添加到链上。就像家谱一样,在一个块被添加到链上之后,除非拥有网络大多数(称为 51%攻击)的计算能力,否则很难改变(入侵)。

  • 家谱提供了家族历史的透明度。同样,区块链允许用户查询整个分类帐或分类帐的一部分,并了解硬币的流动情况。

  • 由于每个家庭都保存了家谱的副本,即使由于自然灾害、战争或其他原因导致许多副本丢失,也不太可能丢失家谱。只要至少有一个家庭幸存下来,家谱就会幸存下来。同样,只要至少有一个节点幸存下来,分散式分类帐就会幸存下来。

虽然家谱是解释区块链一些关键概念的一个好比喻,但它们并不相同。不可避免地,它们之间存在着不共享的特征。例如,区块链广泛使用密码学和哈希来保护数据和阻止黑客。家谱没有这种需求。因此,接下来我们将摆脱家谱的比喻,按照时间顺序解释关键的区块链概念。

比特币

区块链技术最初引起人们的关注是因为比特币区块链,由中本聪在 2008 年 10 月发表在 metzdowd.com 的密码学邮件列表上的一篇白皮书中概述的一个想法。它描述了比特币数字货币BTC),标题为比特币:一个点对点的电子现金系统。2009 年 1 月,中本聪发布了第一个比特币软件,这启动了比特币网络和比特币加密货币的第一个单位:BTC 币。

为什么比特币

比特币的诞生是在 2008 年金融危机之后,这是自大萧条以来最严重的经济危机。这不是巧合。比特币加密货币的发明者旨在解决人们对金融机构的幻灭,其在风险控制方面的史诗般失败导致了 2008 年的金融危机。

金融机构所扮演的一个基本角色是成为一个中间实体,将不信任的各方聚集在一起促成交易。例如,零售银行吸引个人的剩余资金并借给需要资金的个人或公司。支付给资金供应方和借款方的利息之差是银行提供中介服务的费用。金融机构非常成功地提供这些服务,并在全球经济中发挥着关键作用。然而,这种商业模式存在许多缺陷。以下是一些例子:

  • :完成一笔金融交易通常需要数天的时间。例如,完成并结算跨境汇款需要三天时间(在初始输入订单后)。为了使其发生,一个机构内和机构间的多个部门和应用系统必须共同努力促成交易。另一个例子是股票交易。投资者雇佣经纪人输入订单以路由到证券交易所。在这里,经纪人可以是交易所的成员,也可以将订单路由到另一个具有会员资格的中介机构。在交易所的买卖双方找到匹配后,交易细节由两方记录并分别发送到其后台办公室。后台团队与清算所进行清理和结算。对于交易双方来说,完成证券(股票)所有权和现金交换的行动需时 T + 3。

  • 昂贵:金融中介在提供这些服务时通常收取高昂的费用。例如,美国银行可能会收取 10 到 30 美元的费用,以向其他国家的接收者发送资金。在股票交易中,全服务经纪人通常会收取数十美元或更多的费用。即使是折扣经纪人,投资者每笔交易也需要支付 7 到 10 美元。

  • 易受黑客攻击:由于客户和交易细节保存在机构内的集中区域,因此容易受到黑客攻击,造成严重的财务损失或泄露客户的机密个人信息。最近,一些知名公司如 JP 摩根(2014 年有 8300 万账户遭黑客攻击)、Target(2013 年有高达 7000 万客户信息遭黑客攻击)、以及 Equifax(2017 年有 1.48 亿美国消费者信息遭黑客攻击),都发生了高调的个人数据泄露事件。

  • 不透明:金融机构保存有关交易的详细和汇总信息。然而,大部分信息对个体客户不公开,导致信息不对等。以跨境汇款为例,发送方和接收方都要等待三天才能知道交易是否成功完成。如果交易失败,就必须进行漫长的调查。想象一下如果接收方处于紧急情况,需要立即获得资金。尽管客户必须支付高额费用,这样的服务仍是不尽人意的。

利用区块链技术,上述问题得到了优雅的解决。在比特币区块链的情况下,要转移的基础资产是数字硬币 BTC。跨境 BTC 交易最多只需 1 小时即可完成。不需要结算,因为交易和结算在一个动作中完成。该交易的成本仅是通过银行转账的一小部分。例如,美国银行(BoA)最近发布的一份报告称,通过区块链转账的成本是 BoA 收费的 1/6000。然而,对一些客户来说,等待一个小时仍然太长。瑞波,一个全球汇款提供者,可在不到 1 分钟内完成。

词汇“比特币”经常引起混淆,因为人们将该词用于三种事物:加密货币、区块链和协议。为避免混淆,我们使用 BTC 指代加密货币,使用比特币指代区块链和使用分布式账本的相应网络。对于协议,我们将完全拼写比特币协议或简单称为协议。

点对点网络

要解释比特币的工作原理,让我们看看用于完成跨境交易的现有业务模型涉及哪些步骤:

  • 客户通过访问银行分行或通过网络输入订单。发送者提供订单的详细信息,如金额、发送货币、接收者姓名、接收货币、接收者银行名称、账号和分行号、以及一个 SWIFT 号码。这里,SWIFT 代表 全球银行间金融电信协会,一个金融机构用来通过一套标准化的代码系统安全传输信息和指令的消息网络。SWIFT 为每个金融组织分配一个称为 银行识别代码BIC)、SWIFT 代码、SWIFT ID 或 ISO 9362 代码的唯一代码。

  • 发送银行接受订单并验证发送者是否有足够的资金可用。

  • 银行收取费用并通过外汇交易将剩余金额从发送货币转换为接收货币的金额。

  • 发送银行向 SWIFT 输入一条转账信息,包含所有必要信息。

  • 接收银行在收到消息后验证接收者的账户信息。

  • 在成功验证并根据协议在发送和接收银行之间结算资金后,接收银行将金额记入接收者账户。

由于涉及多个步骤、实体和系统,上述活动需要数天才能完成。

比特币网络连接全球各地的计算机。每台计算机都是一个具有平等地位的 节点,除了一部分被称为 矿工 的节点群,他们选择扮演验证交易、构建区块和连接链的角色。在比特币中,完成货币转账的商业模式涉及以下步骤:

  1. 发送者通过一个 电子钱包 输入比特币数量、待转出比特币的地址和待转入比特币的地址。

  2. 交易请求通过电子钱包发送到比特币网络。

  3. 在矿工成功验证交易并将其提交到网络后,比特币现已可供接收者使用。

比特币转账更快(1 小时内,若使用 Ripple,可能更快),原因如下:

  • 交易和结算一步到位。这避免了需要经历耗时且昂贵的对账过程。

  • 由于 BTC 跨国无界限,不需要外汇交易。它可以在全球自由快速地移动。

  • 由于交易不需要中间银行,银行之间不需要资金结算。

在发送方或接收方希望使用美元(USD)、英镑(GBP)、人民币(CNY)或日元(JPY)等法定货币的情况下,可以使用加密货币市场进行比特币与法定货币之间的转换。一个名为 CoinMarketCap 的网站列出了这些市场:coinmarketcap.com/rankings/exchanges/。截至 2018 年 9 月 21 日,有 14,044 个市场。就市值而言,前三名分别是币安(www.binance.com/)、OKEx(www.binance.com/)和火币(www.huobi.pro)。

对等网络可以连接全球节点。然而,仅有物理连接并不足以使两个不信任的交易方进行交易。为了允许他们交易,比特币采取了以下措施:

  • 每个节点保存了分散账本中所有交易的完整副本。这使得对链上交易的任何更改都变得不可行。

  • 账本交易被分组到区块中。非创世区块通过保存所有先前区块交易的哈希与其前一个区块相链接。因此,改变一个交易需要更改当前的交易块和所有后续块。这使得黑客攻击分散式账本变得极其困难。

  • 比特币通过使用工作量证明共识算法解决了双重支付问题,即同一比特币被两次花费的问题。

  • 哈希广泛用于保护各方的身份,并检测出块中发生的任何更改。

  • 公钥/私钥和地址用于掩盖交易各方的身份,并对交易进行数字签名。

凭借这些措施,不信任的交易方因以下原因感到舒适进行交易:

  • 交易是不可变的和永久的。任何一方都无法单方面使交易失效。

  • 不可能发生双重支付。

  • 交易和结算同时发生;因此,不存在结算风险。

  • 身份得到保护。

  • 交易由双方签署,这将避免任何未来的法律纠纷。

密码学和哈希函数

密码学或密码学是研究在对手存在的情况下保护通信技术的技术。在旧时代,密码学与加密同义。现代密码学严重依赖于数学理论和计算机科学。它还利用了其他学科的作品,如电气工程、通信科学和物理学。

密码算法是基于这样的假设设计的,即在可预见的计算硬件进步下,任何对手都不太可能根据这些算法解密加密的消息。换句话说,理论上可以解码加密的消息,但在实践中不可行。因此,这些算法被定义为计算安全的。理论研究(例如并行或整数分解算法)和计算技术的进步(例如量子计算机)可能使这些算法在实践上不安全,因此加密算法需要不断地进行调整。

加密是将明文转换为不可理解的文本,称为密文。解密是反向操作,换句话说是从不可理解的密文转换回明文。

比特币挖掘使用的加密算法是哈希函数。哈希函数是将任意大小的数据映射到固定大小数据的函数。哈希函数返回的值称为哈希值或简称哈希。密码哈希函数允许轻松地验证一些输入数据是否映射到给定的哈希值。然而,反过来—当输入数据是未知的—从哈希值重建输入明文在实践上是不可行的。换句话说,哈希是一个单向操作。哈希函数的另一个显著属性是,输入明文的微小变化将导致完全不同的哈希值。这个特性对于保护信息是可取的,因为黑客对原始数据的任何微小改变都会导致明显不同的哈希。

两种常见的哈希算法是 MD5(消息摘要算法 5)和 SHA-1(安全哈希算法):

  • 由罗纳德·里维斯特在 1991 年开发,MD5 将输入明文映射为一个 128 位的哈希值。MD5 消息摘要校验和通常用于在数字文件传输或存储时验证数据完整性。已发现 MD5 存在广泛的漏洞。

  • SHA-1 是一种密码哈希函数,将输入的明文映射为一个 160 位(20 字节)的哈希值,也称为消息摘要,通常以 40 位十六进制数显示。SHA-1 由美国国家安全局设计,并且是美国联邦信息处理标准之一。

SHA-256 是 SHA-1 的继任哈希函数。它是目前最强大的哈希函数之一,迄今为止尚未以任何方式被破解。SHA-256 为文本生成几乎唯一的 256 位(32 字节)签名。例如,我的测试字符串 映射为 5358c37942b0126084bb16f7d602788d00416e01bc3fd0132f4458d

d355d8e76. 经过微小改动,*我的测试字符串* 的哈希值为 98ff9f0555435`

f792339d6b7bf5fbcca82f1a83fde2bb76f6aa95d66050887cc*,完全不同的值。SHA-256 可以生成 2²⁵⁶ 种可能的哈希值。迄今为止,尚未出现两个不同的输入产生相同的 SHA-256 哈希的情况,这在密码学中称为碰撞问题。即使使用最快的超级计算机,也需要比宇宙的年龄更长的时间才能发生碰撞。因此,SHA-256 被比特币用于加密。

分布式账本、区块、交易、地址和 UTXO

在金融机构,账本是记录财务交易的账簿。类似地,比特币通过地址维护账本,记录比特币交易和余额。一个关键区别是,银行的账本是集中式的,而比特币的账本是分散式的。因此,银行的账本更容易被篡改。另一方面,比特币的账本非常难以篡改,因为必须在全球所有节点上更改账本。

用户提交包含以下信息的交易:

  • 要转移的比特币的来源

  • 要转移的比特币数量

  • 应转移比特币的目标

根据维基网站,交易的一般结构如下所示:

源地址和目标地址都是 64 字符的哈希值。以下是一个地址的示例:979e6b063b436438105895939f4ff13d068428d2f71312cf5594c132905bfxy1

术语地址有点令人困惑。程序员可能认为它是与磁盘或内存位置相关的地址。然而,它与物理位置无关。相反,它是一个逻辑标签,用于将已转移的比特币分组。在某种程度上,可以将其视为银行账户号码,但它们之间存在根本性差异。例如,银行有一个集中的地方,用于保存有关账户的元数据,例如,所有者姓名、账户开立日期和账户类型。此外,账户余额是预先计算并保存的。在比特币中,地址上没有元数据,必须查询整个账本以找到地址的余额,通过计算进出地址的净比特币数量。地址仅在比特币交易中引用。当地址余额降至 0 时,任何从该地址取出比特币的未来请求将由于资金不足而失败交易验证。

比特币利用UTXO模型管理其比特币转账。这个术语是由加密货币引入的,指的是未花费的交易输出。这是一个未被花费且可以作为未来交易输入的区块链交易输出。在比特币交易中,只有未花费的输出可以作为输入,这有助于防止双重花费和欺诈。因此,承诺的交易导致在区块链上删除输入并创建 UTXO 形式的输出。新创建的未花费交易输出可以由持有相应私钥的所有者花费。换句话说,UTXO 持续处理,承诺的交易导致删除已花费的硬币并在 UTXO 数据库中创建新的未花费硬币。

像地址一样,比特币不与任何实物对象(如数字代币文件或实际铸造的硬币)相关联。相反,它只存在于分布式分类账中的交易中。例如,如果一个人想知道迄今为止铸造的比特币总数,就必须查看区块链上所有非零余额地址并加总所有比特币。由于比特币的每个节点都保存着分类账的副本,因此只需要计算时间就可以找到答案。

当用户在节点输入比特币交易请求时,节点上安装的比特币软件将该交易广播给所有节点。网络中的节点将通过检索包含输入地址的所有历史交易并确保这些地址中的比特币是合法且足够的来验证交易的有效性。之后,矿工节点开始通过收集经过验证的交易来构建一个区块。通常,比特币区块包含 1,500 至 2,000 笔交易。赢得解决难题竞赛的矿工将承担构建并链接一个新区块到区块链的角色。在比特币区块链上,大约每 10 分钟创建一个新区块。截至 2018 年 9 月 21 日,在比特币上已经创建了大约 542,290 个区块。比特币区块的结构如下所示:

在这里,区块头包含以下字段:

随机数的概念将在挖矿子章节中进行解释。hashPrevBlockhashMerkleRoot的值相同。Merkle 树哈希根本上是区块中所有交易哈希的哈希,通过二叉树聚合结构进行。以下图解释了这个想法:

共识机制

如果有人用 1 美元购买一瓶水,那个人就不能再用相同的 1 美元购买一罐可乐。如果一个人可以自由地二次花费一美元,货币将变得毫无价值,因为每个人都将拥有无限数量的货币,而赋予货币价值的稀缺性将消失。这就是所谓的双重支付问题。对于比特币而言,双重支付是同一比特币被使用多次的行为。如果这个问题没有解决,比特币就会失去稀缺性,不能用来促进两个不信任方之间的交易。比特币核心网络通过共识机制防止双重支付。要解释比特币共识机制是如何运作的,我们首先介绍PoW(工作证明)和挖矿的概念。

正如前面所解释的,为了获得成为当前新区块的建造者并获得做这项工作的奖励,矿工需要比其他矿工更早地解决一个困难的数学难题。解决数学难题的工作称为PoW

为什么需要 PoW?想象一下:在一个由相互不信任的参与者组成的网络中,为了使网络正常运作,需要比不诚实的攻击者更多的诚实参与者。想象一下,当一个矿工收集到足够的交易来构建一个新的区块时,他被允许立即构建新的区块。这简直就是一个谁能迅速组合足够的交易的比赛。这为恶意攻击者留下了大门,可以通过包含无效或假交易来攻击网络,并总是赢得这场比赛。这将允许黑客自由双重支付比特币。

因此,为了防止攻击者引入恶意交易,参与节点需要足够的时间窗口来验证每笔交易的有效性,确保比特币尚未被花费。由于每个节点都维护着账本的副本,一个诚实的矿工可以追踪历史,确保以下内容以确认交易的有效性:

  • 交易请求者拥有这些比特币。

  • 相同的比特币在账本中没有被任何其他交易所花费过。

  • 相同的比特币在候选区块内没有被其他交易所花费过。

目前,这段时间窗口大约设置为 10 分钟。为了强制执行 10 分钟的等待时间,比特币要求矿工解决一个足够困难的数学难题。这个难题只需要进行简单的计算。矿工需要重复进行相同的计算多次,以烧掉足够的 CPU 时间,达到网络每 10 分钟平均建立一个新区块的目标。重复猜测的过程称为挖矿,而设备(专门制造的)称为矿机

由于为了赢得挖矿竞赛,矿工需要大量投资于硬件,这些矿工专注于挖矿工作,并旨在获得足够的 BTC 来支付挖矿运营成本并获利。截至 2018 年上半年,给予获胜矿工的奖励为 12.5 BTC。可以通过访问 CoinMarketCap 网站(coinmarketcap.com/)找到 BTC 的价格。截至 2018 年 9 月 21 日,一个 BTC 大约以 6,710 美元的价格交易。因此,12.5 BTC 约价值 83,875 美元。

按照比特币协议,挖矿是发行新 BTC 的唯一方式。慷慨地奖励矿工有三个目的:

  • 补偿矿工在硬件上的投资。

  • 包括支付挖矿运营成本,例如水电费用,这可能是由于在矿场部署了大型挖矿设备而显著的,人工工资和场地租金。

  • 给矿工激励,以防止网络遭受恶意黑客攻击。矿工有动力维护比特币网络,以免自己的 BTC 和挖矿基础设施价值损失。如果比特币被黑客攻击,比特币的声誉将受到严重损害,BTC 价格将暴跌。这正是比特币发明者所希望的:有更多的好矿工而不是坏矿工来解决双花问题。

可以发行的 BTC 总数被固定为 2100 万。截至今天(2018 年 9 月 19 日),大约已发行了 1700 万 BTC。比特币协议规定了动态调整支付速率的规则,剩下的 400 万枚硬币预计还需要另外 122 年才能完全挖掘出来。以下是区块创建支付速率如何动态调整的说明:

  • 每 210,000 个区块更改一次速率。它是基于链上的块高度函数,创世=0,并使用 64 位整数运算来计算,例如:(50 * 100000000)>>(高度/210000)。速率最初从 50 BTC 开始,到第 210,000 个区块时下降到 25 BTC。到第 420,000 个区块时下降到 12.5 BTC,当网络达到 6,930,000 个区块时最终将降至 0。

分叉

比特币区块链可以分为两条潜在的路径,因为矿工不一定以相同的方式或同时收集交易和合约区块候选。其他原因,如黑客攻击或软件升级,也可能导致路径分歧。这些分裂的修补程序称为分叉。有临时分叉和永久分叉。

如果由于例如恶意攻击导致永久分叉,则会发生硬分叉。类似地,还有软分叉的概念。硬分叉和软分叉都指对协议的根本性更改。硬分叉使先前无效的区块/交易变为有效,而软分叉使先前有效的区块/交易变为无效。

为了消除临时分叉,比特币协议规定应选择最长链。换句话说,当面对两条路径时,胜出的矿工会选择更长的链来连接新区块。因此,更长的路径会继续增长,而输掉的(更短的)路径上的区块会变成孤立块。比特币节点将会很快丢弃或不接受这些孤立块。它们只保存最长链上的区块作为有效区块。

在永久性分叉的情况下,网络节点必须选择跟随哪个链。例如,比特币现金由于比特币社区就如何处理可伸缩性问题存在分歧而产生分叉。结果,比特币现金变成了自己的链,并与创世区块直至分叉点的交易历史。截至 9 月 21 日,比特币现金的市值约为 80 亿美元,排名第四,而比特币为 2150 亿美元。

挖矿和难度级别

还有一个需要解决的问题:如何保持每 10 分钟一个新区块的速率。如果不做任何处理,由于以下因素,挖矿速率将会产生变化:

  • 网络矿工数量会随着 BTC 价格的波动而变化。

  • 技术进步使矿机逐渐快速起来

  • 挖矿机的总数量不断变化

比特币通过调整数学难题的难度级别来保持每 10 分钟一个新区块的速率。难度级别是根据最近添加的区块的速率计算出来的。如果新的区块添加速率的平均值低于 10 分钟,难度级别将会增加。如果平均速率超过 10 分钟,难度级别将会降低。难度级别每 2,016 个区块更新一次。以下图表显示了比特币难度级别的历史趋势。

图

我们还没讨论实际的挖矿算法。假设当前的难度级别是找到第一个哈希值的开头字符为 0。在比特币中,解决难题的过程,也就是挖矿,需要矿工按照以下步骤进行:

  • 首先,在进行中的区块中找到 SHA-256 哈希值。

  • 如果得到的哈希值的开头是 0,那么矿工解决了难题。矿工将该区块连接到节点上的账本并获取奖励,12.5 个比特币。矿工的节点向所有节点广播这个消息。网络上的所有其他节点和矿工验证这个答案(通过将区块信息加上随机数映射得到相同的哈希值),并验证账本的整个历史,确保区块包含有效交易。

  • 如果通过检查,所有网络节点都会将该区块加入到其账本的拷贝中。矿工们开始着手下一个新区块的工作。

  • 如果获胜的矿工是恶意攻击者并在区块中包含不良交易,则这些交易的验证将失败,其他矿工将不会在他们的分类帐副本中包含该块。他们将继续在当前块上进行挖矿。随着时间的推移,包含不良块的路径将不再是最长的路径,因此不良块将成为孤立块。这基本上是网络中所有节点如何达成共识,只向网络添加良好的块,防止不良块潜入,从而解决双重支付问题。

  • 如果生成的哈希不以 0 开头,那么允许矿工在输入文本中附加一个已知为随机数的序列号,从 0 到输入文本再试一次哈希。

  • 如果生成的哈希仍不包含前导零,矿工将对输入文本添加另一个序列号 1,获得一个新的哈希。矿工会通过这种方式不断尝试,直到找到第一个具有前导零的哈希。

以下是明文和随机数(nonce)如何共同工作的示例。原始明文是输入字符串 ,随机数变化范围为 0 到 1:

  • 输入字符串f23f4781d6814ebe349c6b230c1f700714f4f70f735022bd4b1fb69421859993

  • 输入字符串 05db70bb3ae36e5b87415c1c9399100bc60f2068a2b0ec04536e92ad2598b6bbb

  • 输入字符串 15d0a0f2c69b88343ba44d64168b350ef62ce4e0da73044557bff451fd5df6e96

在比特币中,难度调整 很大程度上指的是改变所需的前导零位数。(实际调整涉及到其他矿工调整到该要求。)每增加一个前导零位数,都将显著增加尝试的平均次数,因此会增加计算时间。这是比特币如何维持平均每 10 分钟添加一个新区块的速率。当前比特币的难度级别是 18 个前导零。

黑客行为 - 51%问题

由于 BTC 价格上涨,挖矿操作变得更具吸引力。投资者纷纷涌入,成千上万的矿池加入网络,以在竞赛中率先解决难题并获取奖励优势。对于没有大型资本投资的玩家,他们可以选择参与矿池。当矿池赢得一场竞赛时,奖励将根据贡献的计算能力分配给每个参与者。

矿池的这种不断增长的计算能力构成了所谓的51%问题的真正威胁。当一个矿工成功地将计算能力积累到至少等于网络总计算能力的 51%时,就会出现这个问题。这时,矿工将有机会领先其他矿工。由于这个矿工有超过 50%的机会首先解决谜题,所以矿工可以继续增加包含不良交易的区块来扩展账本。很快,恶意矿工的账本将变得最长,并且所有其他节点都必须根据比特币的共识协议保存此路径。

对于比特币这样的大型和成熟的网络,51%问题并不是一个关键问题,主要原因如下:

  • 一个建立良好的网络将吸引更多的参与方,并连接非常多的节点。对于一个已经成熟的网络,黑客购买所需的挖矿设备需要巨额的初始投资。当这样的网络受到攻击时,当消息公开后,加密货币的价格会迅速下跌,黑客将很难收回投资。

  • 在比特币的历史上,曾经有矿池积累了危险的高计算能力接近这条线的情况。当参与的矿工意识到问题时,许多人选择离开该矿池。很快,矿池的计算能力降到了一个安全水平。

  • 对于一个小而不成熟的网络,矿工很容易集结超过 51%的计算能力。然而,这些网络的加密货币价值微不足道,对黑客来说几乎没有任何财务激励来利用 51%的问题。

私钥和比特币钱包

正如前面讨论的那样,比特币并不存在于物理上。它们存在的唯一证据是当它们与地址关联时,在交易中被提及。当地址被初始创建时,会生成一对公钥和私钥。公钥对公众公开,而私钥仅由地址所有者保留。当所有者想要花费他们的 BTC 时,所有者使用私钥签署数字签名,并将 BTC 请求发送到比特币网络。换句话说,想要花费 BTC,必须同时知道地址和它的私钥。

如果所有者丢失私钥,与之相关的 BTC 将永久丢失。因此,建议将此信息存放在安全的地方。通常最好将地址和私钥分开存放。为了防止数字副本丢失,所有者应保留纸质打印的物理副本。为了使转换更容易,所有者可以打印一个 QR 码,以后在需要时扫描该 QR 码。

比特币钱包应用程序可用于帮助用户管理密钥和地址。可以使用钱包执行以下操作:

  • 生成地址和相应的公/私钥

  • 保存和组织 BTC 的信息

  • 向比特币网络发送交易请求

在比特币中,私钥是一个 256 位长的哈希,公钥是 512 位长。它们可以转换为十六进制表示中的较短长度。以下截图提供了一个公/私钥对和一个地址的示例:

图片

比特币私钥也可以用由 5 和一个字符 51 开始的字符串表示,公钥可以用由 72 个字符的字符串表示。一个示例私钥是 `5Jd54v5mVLvy

RsjDGTFbTZFGvwLosYKayRosbLYMxZFBLfEpXnp 和一个示例公钥是 BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68

比特币脚本

一个人可以为编程比特币操作安装以下开发工具:

  • NodeJS:这是一个开源的、跨平台的 JavaScript 运行时环境,用于在浏览器之外执行 JavaScript 代码。它允许程序员快速、轻松地编写和执行脚本。这些脚本可以编写为在 Web 浏览器或服务器上运行。

  • BitcoinJS:这是一个用于处理比特币及其密码功能的 JavaScript 库。BitcoinJS 可用于生成公钥/私钥和地址。

  • Blockchain.info:这是一个公共 API,可用于查询区块链以查找余额并将交易广播到网络。它可用于实现比特币节点并安装和运行比特币节点。

安装完上述工具后,可以执行以下操作:

  • 生成一个新的私钥并计算一个公钥

  • 检查某个地址的余额

  • 生成地址

  • 构建一个新的交易

  • 发送一个交易,涉及三个步骤:

    • 构建一个带有输入和输出列表的交易

    • 使用必要的私钥对交易进行签名

    • 将交易广播到网络

  • 建立一个托管账户

  • 广播交易

替代币

感谢比特币,区块链技术已经引起了全球的关注。就像任何新技术一样,它也有其局限性。许多比特币的变种被创建来解决比特币的特定限制。在这里,我们提到其中一些:

  • 比特币现金:这是比特币链的硬分叉,是因为一群比特币核心开发者想要使用不同的方法来解决可扩展性问题而创建的。

  • 莱特币:这几乎与比特币相同,只是添加新区块的时间从 10 分钟减少到 2 分钟。

  • Zcash:这基于比特币,但提供了完全的付款保密性。

  • 门罗币和 Zcash:这两种替代币通过使交易历史不可追踪来解决隐私问题,但它们实现了两种不同的解决方案。

  • Dash:主要改进了用户友好性。例如,交易变得无法追踪,用户不必等待添加了几个额外新区块后才考虑将交易提交到链上。

  • Namecoin:这扩展了比特币的用例,后者仅用于交易 BTC,提供域名服务。

  • Peercoin:这个山寨币解决了 PoW 的缺陷,后者环境不友好且吞吐量低。相反,它采用权益证明来实现共识。根据这一规则,矿工根据持有的币数验证区块交易。换句话说,矿工的挖矿能力与持有的 Peercoin 数量成比例。

  • Primecoin:Primecoin 矿工竞相寻找下一个最大质数。

以太坊

尽管上述山寨币采取了一些措施来解决比特币的一部分限制,但仍然存在一些尚未解决的基本问题:

  • 比特币和这些山寨币都特定于一个目的:交易 BTC 或山寨币。

  • 尽管程序员可以使用诸如 BitcoinJS 之类的工具与网络交互,但结果代码位于区块链之外,并不能保证运行。链本身没有用于直接在区块链上编码的图灵完备编程语言。

  • 这些区块链是无状态的,人们必须在整个账本中搜索才能找到答案,例如已铸造的 BTC 总数。

针对这些问题,加拿大加密货币研究员和程序员 Vitalik Buterin 在 2013 年底提出了以太坊的想法。通过在线众筹资助,该系统于 2015 年 7 月 30 日上线,为众筹预挖了 1190 万枚币。

以太坊的核心思想是构建一个通用的区块链,以便用户可以解决各种业务问题,不仅限于加密货币转移。以太坊引入了一些新的关键概念:

  • 在区块链上保存智能合约的概念

  • 使用 Solidity 等图灵完备编程语言实现智能合约的概念,并在区块链上运行代码片段

Solidity 最初由 Gavin Wood 在 2014 年 8 月提出。以后,由 Christian Reitwiessner 领导的以太坊项目 Solidity 团队开发了这种语言。它是针对以太坊虚拟机EVM)设计的五种语言之一(Solidity、Serpent、LLL、Vyper 和 Mutan)。

程序员和律师 Nick Szabo 在 1996 年首次提出了术语智能合约。在他的博客中,Nick Szabo 将其描述为所有智能合约的始祖,即自动售货机。

今天的区块链上的智能合约与自动售货机具有完全相同的属性。自动售货机是根据硬编码的规则构建的,这些规则定义了在满足某些条件时执行的操作,例如:

  • 如果苏珊在自动售货机中放入一张美元钞票,那么她将收到一袋椒盐脆饼。

  • 如果汤姆放进一张五美元的钞票,他将收到一袋椒盐脆饼并且会得到四美元的零钱。

换句话说,规则由自动售货机物理上定义并执行。同样,智能合约包含在区块链上运行的程序代码中的规则,当满足某些条件时触发。

智能合约概念的引入具有重要意义:

  • 智能合约是一种脚本化的法律文件。

  • 写入合同的代码存储在以太坊区块链上,不能被篡改或删除。这极大地增加了法律文件的可信度。

  • 这段代码无法被停止,意味着任何一方——不管这个方有多么强大——都无法命令或干预智能合约代码的运行。只要满足一定条件,代码就会运行,法律上定义的行动也将被完成。

  • 以太坊对于区块链就像操作系统对于计算机一样。换句话说,这个平台是通用的,不再只服务于特定的目的。

  • 现在它拥有了一种图灵完备的语言:Solidity。

企业区块链 - Hyperledger

以太坊的到来彻底改变了区块链技术。将技术应用于超越金融行业的商业问题已经成为可能。然而,以太坊并不足够的场景还有很多。以太坊的问题包括以下内容:

  • 真正的企业应用程序,特别是在金融行业,需要高吞吐量,这意味着每天可能有数十亿的交易。当前形式的以太坊每天的最大容量为 140 万笔。比特币甚至更糟糕:每天 30 万笔交易。在压力测试期间,比特币现金达到了 220 万笔。正在开发中的以太坊 2.0 目标是达到每天 10 亿笔的交易量,同时保持去中心化和安全的公共区块链。

  • 许多金融市场,比如场外衍生品或外汇交易,都是基于许可的。以太坊或比特币支持的公共区块链不符合这种需求。

为了满足他们的需要,各行各业的知名公司成立联盟,共同开展基于许可的企业区块链项目。换句话说,节点必须在加入区块链网络之前获得批准。企业区块链的例子有 Hyperledger 和 R3 的 Corda。

2015 年 12 月,Linux FoundationLF)宣布了 Hyperledger 项目的创建。其目标是通过开发区块链和分布式分类账来推动跨行业合作。2017 年 7 月 12 日,该项目宣布推出了可供使用的Hyperledger FabricHF)1.0。

目前,Hyperledger 包括五个区块链框架:

  • Hyperledger FabricHF):一个许可区块链,最初由 IBM 和 Digital Asset 贡献,旨在成为以模块化架构开发应用程序或解决方案的基础。它采用插件组件提供功能,如共识和成员服务。与以太坊类似,HF 可以托管和执行智能合约,称为链代码。HF 网络由对等节点组成,这些节点执行智能合约(链代码),查询分类账数据,验证交易,并与应用程序交互。用户输入的交易被通道化到一个命名为排序服务组件的服务,该服务最初用作 HF 的共识机制。称为排序节点的特殊节点验证交易,确保区块链的一致性,并将已验证的交易发送到网络的对等节点以及实施为证书颁发机构的成员服务提供商MSP)服务。

  • Hyperledger Iroha:基于 HF,专为移动应用设计。Iroha 由 Soramitsu、日立、NTT 数据和 Colu 贡献。它采用了现代化和领域驱动的 C++设计。它实现了一种名为 Sumeragi 的共识算法。

  • Hyperledger Burrow:最初由 Monax 和 Intel 贡献,Burrow 是一个模块化的区块链,是按照 EVM 规范构建的客户端。

  • Hyperledger Sawtooth:由 Intel 贡献,实现了一种名为Proof of Elapsed TimePoET)的共识算法。PoET 旨在尽可能高效地实现分布式共识。Sawtooth 支持许可和非许可网络。Sawtooth 设计灵活多变。

  • Hyperledger Indy:最初由 Sovrin 基金会贡献,旨在支持分布式分类账上的独立身份。Indy 提供工具、库和可重用组件,这些组件旨在提供数字身份。

该倡议的早期成员包括以下几个:

  • 区块链独立软件供应商(Blockchain、ConsenSys、Digital Asset、R3、Onchain)

  • 技术平台公司,如思科、富士通、日立、IBM、英特尔、NEC、NTT DATA、红帽和 VMware

  • 金融机构,如荷兰银行、澳新银行、纽约梅隆银行、CLS 集团、芝加哥商品交易所集团、美国存管与清算公司(DTCC)、德国证券交易所集团、摩根大通、美国富国银行

  • 软件公司,如 SAP

  • 学术机构,如剑桥替代金融中心、哥伦比亚区块链和加州大学洛杉矶分校区块链实验室

  • 系统集成商及其他公司,如安永、Calastone、Wipro、Credits、Guardtime、IntellectEU、Nxt Foundation 和 Symbiont

区块链的演变

区块链技术仍处于早期阶段。在它成熟并充分发挥潜力之前,还需要很多年的时间。目前,还没有普遍认可的方式来分类或定义区块链的世代。

在她关于区块链的书中,Melanie Swan 基于区块链平台创建的使用场景将区块链 1.0 到 3.0 进行了定义。

"区块链 1.0 是货币,部署加密货币在与现金相关的应用中,比如货币转移、汇款和数字支付系统。

区块链 2.0 是合同,使用区块链进行更昂贵的经济、市场和金融应用的完整 slate:股票、债券、期货、贷款、抵押贷款、所有权、智能财产和智能合同。

区块链 3.0 是超越货币、金融和市场的区块链应用,特别是在政府、健康、科学、文化和艺术领域。

有些人将区块链发展划分为从区块链 1.0 到 4.0 的四代:

  • 区块链 1.0:以比特币为此领域最突出的例子,在此段落中,使用案例基于分布式分类技术(DLT),其中可以执行金融交易。加密货币被用作互联网上的现金。

  • 区块链 2.0:以太坊是此领域最突出的例子,其中新的关键概念是智能合约,这些合约存储并在区块链上执行。

  • 区块链 3.0:关键词是 DApps,即去中心化应用的缩写,它们避免了集中式基础设施。它们使用去中心化存储和去中心化通信。与仅涉及后端或服务器端代码的智能合同不同,DApp 可以具有前端代码和用户界面,即客户端代码,用于与其在区块链上的后端代码进行交互。与智能合同代码一样,DApp 的前端可以存储和在以太坊的 Swam 上执行。总之,DApp 是前端加上在以太坊上运行的合同。

  • 区块链 4.0:此领域的区块链平台旨在为工业 4.0 提供服务。简单来说,工业 4.0 指的是自动化、企业资源规划和不同执行系统的集成。

无论区块链技术如何划分版本,可以肯定的是技术的增长远未结束。新的想法和实施将被纳入现有平台,以应对解决现实问题的挑战。换句话说,区块链技术将变得灵活,并且能够自我调整,成为解决商业问题的推动者。

摘要

区块链是一种新兴技术。由于其不可变性、透明性、避免双重支付的共识机制以及其他巧妙的设计,比如将块与前一个块的哈希链接在一起,这项技术使不信任的各方能够相互交易。在本章中,我们解释了其重要特性的基本概念。大部分讨论都集中在比特币上,它是这项技术的鼻祖。我们简要讨论了以太坊,它扩展了比特币并引入了智能合约的概念。智能合约的引入使以太坊区块链变得通用,使我们能够开发超越比特币被发明的无国界现金支付用例的应用程序。企业链的概念以及其中的一个例子——Hyperledger 也被提及。最后,我们简要介绍了区块链的演进,以使读者对技术的趋势有所了解。在下一章中,我们将详细讨论以太坊的概念。

第十三章:以太坊基础知识

以太坊是一个开源的公共区块链,被认为是比特币的替代币。一位加拿大的加密货币研究员和程序员 Vitalik Buterin 在 2013 年底提出了这个想法。该平台于 2014 年中期通过在线众筹成立,并于 2015 年 7 月底上线。2016 年的 DAO 事件 导致了硬分叉,结果分为以太坊ETH)和以太经典ETC)。

在本章中,我们涵盖了以下关于以太坊的主题:

  • 以太坊概述

  • 基本概念,如以太、ERC20 代币、智能合约、EVM、gas、账户和预言机

  • 以太坊性能问题以及解决该问题的持续努力,如 PoS、Casper、Plasma 和 Sharding

以太坊概述

2013 年底,Vitalik Buterin 向区块链社区发送了一封电子邮件,宣布一份概述以太坊想法的白皮书。他将其描述为一个具有内部语言的通用平台,因此任何人都可以编写应用程序。根据 Vitalik 的说法,以太坊最初的想法是创建一个面向金融科技的通用区块链。以太坊是比特币的一种变体。与专注于支付的比特币不同,以太坊是一种可编程的通用区块链。智能合约的引入是以太坊与比特币区分的关键。

一个众所周知的类比来描述以太坊和智能合约,将不信任的交易方聚集在一起交易数字或数字化的实物资产,就像 第十二章 结尾所述的自动售货机,区块链技术简介

制作自动售货机后,包括机器所有者在内的任何人都无法更改规则。购买者在交易前或交易期间不需要担心所有者更改规则。因此,购买者可以信任机器按预期的方式行事,并且感到足够舒适以继续交易。当然,自动售货机未必提供完美的解决方案。顾客偶尔可能会遇到故障的机器并插入 1 美元,但什么也不发生。如果自动售货机没有提供退款解决方案,例如发布联系电话,顾客将永远失去 1 美元。另一方面,以太坊的解决方案要坚固得多。智能合约形式的规则分布到所有节点。同样的智能合约将在全球数千个(甚至更多)节点上几乎同时运行。只要至少有一个节点在运行,交易就会成功执行。换句话说,以太坊真正是一台全球计算机。

一些区块链爱好者回应了 Vitalik 的电子邮件,并组成了一个核心团队来推进并执行这个想法。 (这篇开创性的论文,标题为A Next-Generation Smart Contract and Decentralized Application Platform,可以在github.com/ethereum/wiki%20Wiki/上获得,截至 2018 年 8 月 22 日,经过 169 次修订,该网址已存档,原文发表于 2015 年 3 月 28 日。)2014 年 1 月,成立了以太坊基金会。不久之后(2014 年初),一位英国计算机科学博士 Gavin Wood 发表了一篇名为Ethereum: A Secure Decentralized Generalized Transaction Ledger的黄皮书(ethereum.github.io/yellowpaper/paper.pdf)。Gavin 的论文统一了多个实现以太坊想法的努力,并成为未来开发工作的蓝图。

在谈论以太坊的众筹活动之前,我们需要首先解释众筹的概念。众筹指的是通过从大量人群中募资,通常在互联网上,来为项目或倡议筹集资金的做法。众筹是一种替代的筹资方式。在区块链项目的情况下,众筹通常是以项目所有者出售预先设定的(预挖矿的)总量数字货币的一部分,将它们交换成法定货币或其他已建立的数字货币(如比特币)。

从 2014 年 7 月至 8 月,进行了一次在线众筹活动。这次活动导致了预挖矿以太——以太坊的本地加密货币,总量 1190 万枚代币的销售。这约占以太币总量的 12%:102,431,467 枚。通过这次众筹销售的收入,开发工作开始了。核心以太坊团队包括 Vitalik Buterin,Mihai Alisie,Anthony Di Iorio 和 Charles Hoskinson。以太坊项目的真正开发是由一家名为 Ethereum Switzerland GmbH(EthSuisse)的瑞士公司启动的。该平台于 2015 年 7 月 30 日上线。

Stephan Tual,一位前以太坊首席市场官,在 2016 年 4 月 30 日成立了一个名为The DAO的公司。这个实体的目的是管理选择部署哪个智能合约的流程。The DAO提出了一个聪明的想法,基于投资来选择合约。完成的智能合约会被发布在互联网上。潜在投资者将宣布要投资多少金额到一个智能合约中。投资金额最多的智能合约将被选择部署。The DAO通过众筹销售筹集了创纪录的 1.5 亿美元资金用于此项目。The DAO在六月被黑客攻击,由于软件中的漏洞损失了 5 千万美元价值的以太币。这次黑客攻击引发了以太坊社区内一场激烈的讨论,探讨如何处理这件事。出现了两种相争的观点:

  • 加强以太坊代码,使未来类似攻击不可行,并将代码部署到所有节点

  • 不对核心以太坊代码进行任何更改,冒着未来攻击的风险

Vitalik 呼吁进行硬分叉解决方案,并公开要求所有以太坊节点停止交易以部署修补代码。几小时后,全球数千节点完全关闭。大多数节点投票赞成采取硬分叉方法,并用修补程序升级了它们的核心以太坊代码,但仍有一小部分节点选择不采用修补程序,继续运行相同的代码。

这一硬叉事件将以太坊区块链分为两条。运行旧代码并维护原始区块链的节点变成了以太坊经典,代币符号为 ETC,而运行修补代码并维护分叉以太坊区块链的节点变成了以太坊,代币符号为 ETH。硬叉发生在区块编号为 1,920,000。硬叉在这两个网络之间造成了竞争。如今,ETH 价格涨了 130 多倍,而 ETC 只值 ETH 价格的十分之一,这是因为其不受欢迎以及担心未来发生类似The DAO攻击的担忧。

The DAO硬叉之后,以太坊在 2016 年第四季度进行了两次分叉来处理新的攻击。虽然硬叉解决了过去黑客的攻击,但显然这并非可持续发展的解决方案,因为不能总是依靠创建硬叉来解决未来的每一次攻击。因此,以太坊通过阻止黑客的新垃圾邮件攻击来增强自己的保护。

硬叉用于解决黑客攻击,以太坊使用软叉进行协议升级,这些升级是影响以太坊基础功能和/或激励结构的重要变化。一些值得注意的软叉如下:

  • 家园用于改进交易处理、燃气定价和安全性。这一软叉在 2015 年 7 月 31 日进行。

  • 都市部分 1: 拜占庭用于减少以太坊虚拟机的复杂性并为智能合约开发者增加更多灵活性。这一软叉在 2017 年 10 月 16 日进行。

  • 未来还计划了两次协议升级:都市部分 2君士坦丁堡为过渡到股权证明奠定了基础。

2017 年 3 月,区块链初创公司、研究团体和主要公司共同创建了由 30 位创始成员组成的企业以太坊联盟EEA)。五月份,这个非营利组织扩大,纳入了 16 家知名企业成员,如康奈尔大学的研究团体、三星 SDS、微软、英特尔、摩根大通、DTCC、德勤、安永、桑坦德银行、纽约梅隆银行、安海斯集团和加拿大国家银行。到了 2017 年 7 月,成员名单增加到了 159 位。

尽管自其最初推出以来已经进行了许多改进,以太坊仍在不断发展。以太坊 2.0 旨在解决最薄弱的环节之一,即可扩展性,并预计将于 2019 年分阶段推出,正如 Vitalik 最近的评论所示。

以太坊基本概念

以太坊在比特币区块链的基础上构建,包括包含链接块的分布式账本、工作证明算法等关键功能。然而,它最大的增加是引入了能以图灵完备的脚本语言编写的智能合约。由于这一新的增加,与比特币或其非智能合约的亲属不同,以太坊允许开发人员解决通用业务问题。

在讲解基本概念之前,我们总结一些有用的以太坊事实如下:

  • 以太坊有三个主要组成部分:

    • 去中心化:用来保证执行

    • 哈希值:用来保护世界状态

    • 签名:用于授权程序和交易

  • 由于以太坊是一个区块链,它使用数学算法来替代中间实体,并将不信任的各方聚集在一起做生意。

  • 以太坊区块链通过其共识机制在节点上验证数据的有效性,从而为数据带来了信任。

  • 它使用总体验证来替代中央控制。

  • 与交易一样,部署智能合约需要数字签名。已部署的智能合约是永久且不可变的。

  • 智能合约会被分配一个地址。

  • 假设一个智能合约存在漏洞并需要修复。修补后的智能合约将被部署在一个新分配的地址上,并且因此被视为完全独立于旧合约的全新智能合约。

  • 2017 年 5 月,以太坊全球可达节点数量为 25,000 个,包括全节点和轻节点。

  • 全节点已下载并可用完整区块链。以太坊账本可以被修剪。全节点验证在构建区块中的交易。矿工节点必须是一个全节点。

  • 轻节点不存储整个区块链,但是它从它信任的某个人那里存储它关心的部分。

  • 合约代码的脚本通过以太坊虚拟机EVM)在全节点上执行。智能合约的地址存储了在 EVM 上运行的名为操作码的字节码。

  • 由于一个智能合约在数以万计的机器上的全节点上运行,它真正是全球性的。换句话说,将智能合约写入区块链是全球性的和永久性的。

  • 由于智能合约脚本以分散的方式存储,这提供了额外的安全层。所有全节点都知道其他节点存储相同的代码。对于黑客来说,向全球所有良好的节点推送恶意脚本并崩溃它们是不可行的。

  • 智能合约是一份脚本化的法律文件,并且它是执行保证的。因为智能合约在部署时被签署,调用它的交易也被签署,所以在交易的两个交易方之间不应该发生争议。换句话说,有了像以太坊这样的去中心化区块链,对于法官的需求消失了!节点并且可以发展成一个完整网络。

  • 由于永久性和不可变性的特性,以太坊区块链上的数据和程序是可审计的。这对于政府执行监管和合规要求可能具有特殊的兴趣。

  • 以太坊是开源的。任何人都可以下载代码并创建自己的以太坊网络版本。当然,问题在于如何说服他人加入网络以体验其价值。

  • 以太坊是去中心化的。因此,没有控制或指挥整个网络的主节点。网络通过共识运行,按照其协议。

  • 以太坊还提供了容错能力。只要在灾难性攻击期间至少有一个完整节点存活,网络就可以从幸存节点重新构建并发展成完整网络。

  • 尽管以太坊提供了极高的健壮性,但其背后的问题是当其失控时如何停止它。就像The DAO黑客事件的例子一样,网络不得不依赖维塔利克及其权威来彻底关闭它。与今天相比,那时的网络规模要小得多。随着网络规模的不断增长,这种方法将变得更加困难。未来,网络可能会增长到数千万个节点甚至更多。只要有一个节点不响应权威调用,以太坊网络仍然活跃。换句话说,完全关闭网络变得极为困难。当然,这就是去中心化区块链的全部意义所在:没有中心化的权威来指挥其他人!

  • 以太坊允许对其他智能合约进行递归调用。编写不良的智能合约可能导致无限循环。为了解决这个问题,以太坊引入了一个断路器机制,即 Gas,后面将详细解释。

  • 在大数据平台上,一个任务被分成分块分配给网络上的节点,工作由节点共同完成。然而,以太坊的完整节点执行相同的脚本片段。这意味着以太坊区块链的每个完整节点都存储并计算相同的数据;这是可靠但不可扩展的。可扩展性问题是以太坊面临的主要批评之一。正如我们稍后将讨论的那样,已经有多个努力在进行中以解决这个问题。

  • 以太是以太坊的本地加密货币。以太坊允许用户发行自己的数字货币,称为代币。ERC-20/ERC-721/ERC 1400 是发行以太坊代币时应遵循的常见技术标准。

  • 以太坊可以被视为互联网的第三代。这可能是以太坊的 JS API 被称为 Web3 的一个原因。有关使用区块链技术重写互联网的讨论正在进行。

  • 在去中心化互联网上提供集中化服务的做法(例如 Google 在去中心化互联网上提供集中式搜索功能)也将适用于区块链。

以太

由于以太坊是建立在比特币之上的,它被认为是比特币替代币。与比特币类似,以太坊中的 Ether 相当于比特币中的 BTC。在提到协议、区块链、客户端软件和主网时会使用以太坊这个名称。

以太坊主网络是客户端用于将数字资产从发送方转移到接收方的区块链网络。换句话说,这是实际交易在分布式分类帐上发生的网络。主网络相当于生产环境。以太坊测试网络是用于开发的。如www.ethernodes.org/network/2上所述,截至 2018 年 10 月 8 日,主网络有 13,662 个节点,测试网络有 29 个节点。由于实际交易发生在主网络上,因此 Ether 只在以太坊主网络上具有真正的价值。换句话说,在测试网络上,它一文不值。Ether(ETH 和 ETC)在数以万计的数字货币市场上上市和交易。它们的价格变化很大。例如,在 2018 年 10 月 8 日,ETH 的交易价约为 $223,ETC 为 $11.

Ether 可以在地址(账户)之间转移。它用于支付矿工的计算工作,他们会通过交易费用和执行交易产生的燃气消耗来获得报酬。在这里,燃料的概念对于以太坊至关重要,稍后将更详细地讨论。

Ether 是最高面额的货币。还有其他单位。最小的被称为 WEI,以数字货币先驱 Wei Dai 命名,他是 B-money 的发明者。B-money 是他对匿名、分布式电子现金系统的提案。其他单位包括 Gwei、microether 和 milliether。它们都有第二个名称。例如,milliether 也被称为 finney,以数字货币先驱 Harold Thomas Finney II 命名,他在 2004 年撰写了世界上第一个实施的加密货币 RPOW(可重复使用的工作凭证),在比特币之前。以下表格给出了 ether 与其他单位之间的转换率:

Unit Wei value Wei
Gwei (shannon) 10⁹ Wei 1,000,000,000
microether (szabo) 10¹² Wei 1,000,000,000,000
miliether (finney) 10¹⁵ Wei 1,000,000,000,000,000
ether 10¹⁸ Wei 1,000,000,000,000,000,000

ERC20 代币

以太坊是一个通用区块链。它允许开发者构建 DApp 并交易数字资产。相应地,它允许开发者定义一个称为代币的用户特定硬币。其中大部分代币都是 ERC20 代币。ERC 指的是以太坊意见征求,20 是分配给此意见征求的编号。换句话说,ERC-20 是以太坊区块链上用于实施代币的智能合约技术标准。根据 Etherscan.io,截至 2018 年 10 月 8 日,在以太坊主网络上发现了 125,330 个 ERC-20 兼容代币。

ERC-20 为以太坊代币定义了一系列规则。通过这样做,它允许以太坊代币在更大的以太坊生态系统内进行交互和转换。目前,Ether 不符合 ERC-20 标准。但是,由于 Ether 是以太坊的本地币,因此可以转换为其他代币。ERC-20 规范定义了包含方法和事件的接口。

以下是所需方法列表(github.com):

  • name: 返回代币的名称,例如,HelloToken: function name() view returns (string name)

  • symbol: 返回代币的符号,例如,HTC: function symbol() view returns (string symbol)

  • decimals: 返回代币使用的小数位数;例如,8 表示将代币金额除以 100,000,000 以获得其用户表示:function decimals() view returns (uint8 decimals)

  • totalSupply: 返回总代币供应量:function totalSupply() view returns (uint256 totalSupply)

  • balanceOf: 返回另一个账户的账户余额,具有address _owner: function balanceOf(address _owner) view returns (uint256 balance)

  • transfer: 它将指定数量(_value)的代币转移到_to地址,并且必须触发转账事件。如果_from账户余额不足以支出,则应该抛出错误:function transfer(address _to, uint256 _value) returns (bool success)

  • transferFrom: 它将指定数量(_value)的代币从_from地址转移到_to地址,并且必须触发 Transfer 事件。除非_from账户已经通过某种机制明确授权了消息的发送者,否则应该抛出错误:function transferFrom(address _from, address _to, uint256 _value) returns (bool success)

  • approve: 允许_spender多次从您的账户提取,最多到_value金额。如果再次调用此函数,则将当前授权额覆盖为_valuefunction approve(address _spender, uint256 _value) returns (bool success)

  • allowance: 返回_spender仍然被允许从_owner提取的金额:function allowance(address _owner, address _spender) view returns (uint256 remaining)

所需事件列表如下:

  • transfer: 在代币被转移时必须触发,包括零值转移。创建新代币的代币合约应该在创建代币时触发Transfer事件,并将_from地址设置为 0x0:event Transfer(address indexed _from, address indexed _to, uint256 _value)

  • approval: 在任何成功调用 approve(address _spender, uint256 _value)时必须触发:event Approval(address indexed _owner, address indexed _spender, uint256 _value)

尽管以太坊允许一个人创建自己的货币,但以太坊真正的价值在于其对智能合约的保证执行。以太币和 ERC20 代币的创建主要是为了支持项目的初创资金,并在交易过程中用于支付以规避银行。没有真正的商业用例,代币一文不值。

智能合约

术语智能合约最初由尼克·萨博创造,他是一位计算机科学家、法律学者,也是比特黄金(Bit Gold)的发明者,在 1994 年提出。他因其对数字合同和数字货币的研究而成为加密货币世界的传奇人物。一些人甚至认为他是中本聪。尽管他拒绝了这一说法。

尼克·萨博最初定义了智能合约如下:

"智能合约是一种执行合同条款的计算机化交易协议。智能合约设计的一般目标是满足常见的合同条件(例如付款条件、留置权、保密性,甚至执行),最小化恶意和意外异常,并尽量减少对受信任中介的需求。相关的经济目标包括降低欺诈损失、仲裁和执行成本以及其他交易成本。"

在自动售货机上,交易规则内置在机器硬件中。数字资产的交易规则内置在脚本中。也就是说,智能合约由代码组成。以下是一些关于智能合约的有用事实:

  • 智能合约是不可变的。

  • 智能合约是永久的。

  • 智能合约具有时间戳。

  • 智能合约是全球可用的。

  • 智能合约是数字化的法律文件。

  • 智能合约是一种旨在在交易方之间数字化促进、验证或强制执行协议的计算机协议。

  • 智能合约允许在没有第三方作为中介的情况下执行交易。这些交易是可审计和不可逆转的。

  • 智能合约可以移动数字硬币,执行传统支付,或转移数字资产,甚至提供现实世界的商品和服务。

  • 对于涉及第三方的商业交易,例如购买/出售房屋,经常使用第三方担保账户来暂时存储交易双方的资金。有了智能合约,就不需要担保账户。智能合约消除了担保账户的需要,因为它们被保证用于转移资金和资产。

  • 智能合约比传统合同法提供了更多的安全性,其交易成本仅为与合同相关的其他交易成本的一小部分。

  • 在以太坊基金会使用的解释中,智能合约并不一定指的是传统合约的经典概念。它可以是任何类型的计算机程序。

  • 要部署和运行智能合约,必须对部署进行数字签名,类似于在以太坊区块链上发送其他数据或交易。

  • 智能合约可以是公开的,对开发者开放。这引发了一个安全问题。如果智能合约存在缺陷或安全漏洞,所有开发者都能看到。更糟的是,由于其不可变性,这种缺陷或漏洞不易修复。这给黑客大量时间来探索弱点并对以太坊区块链发起攻击。The DAO事件就是这个问题的一个高调例子。

以太坊智能合约可以使用四种语言之一开发:solidity(受 JavaScript 启发)、Serpent(受 Python 启发,不再使用)、LLL(受 Lisp 启发)和 Mutan(受 Go 启发,不再使用)。无论使用哪种语言,智能合约都是用高级编程语言编写的,需要编译成低级的、可在机器上运行的语言。在以太坊智能合约实施中,采用了类似于 Java VM(JVM)的 VM 方法。以太坊的 VM 被称为EVM。智能合约脚本被转换为可在 EVM 上运行的代码,称为字节码。然后,操作码被部署到以太坊区块链上执行。目前,一种以研究为导向的语言正在开发中,这种语言被称为 Vyper,是一种基于 Python 的强类型语言。

以太坊虚拟机

在六十年代,当计算机刚刚发明时,编程使用的是较低级别的语言,例如汇编语言(assembler)。例如,汇编代码行ADD R1 R2 R3,是一个指令,用于将寄存器 1 和寄存器 2 的内容相加,结果放在第三个寄存器,R3 中。寄存器是内置在 CPU 中的临时存储区。对于 32 位 CPU,寄存器是 32 位长的。

然后,汇编语言中的代码被转换为 0 和 1 序列的机器语言,这是机器可执行的。使用低级语言编码是乏味且耗时的。当发明了像 ALGOL 或 BASIC 这样的高级语言时,编码时间大大缩短。然而,底层过程仍然相同:将代码编译成 0 和 1 序列的机器可执行语言。Java、Python、JavaScript 和 C++目前是流行的高级语言。

虽然编译方法效果不错,但它确实有一个不便之处:缺乏可移植性。在一台计算机上编译的代码是机器依赖的。换句话说,它不是可移植的。为了解决这个问题,引入了虚拟机的概念。虚拟机VM)是对计算机系统的模拟。虚拟机有两种类型:系统虚拟机(也称为全虚拟化),它提供了一个真实机器的替代品;以及进程虚拟机,用于在平台独立的环境中执行计算机程序。我们前面讨论的 VM 过程指的就是这个。

用高级语言编写的程序被编译成虚拟机可执行代码。只要计算机支持这样的虚拟机,编译后的代码就可以在上面运行,无需重新编译。例如,JVM 是一个众所周知的 Java 虚拟机,它使计算机能够运行编译成 Java 字节码的 Java 程序。

在以太坊的情况下,智能合约是用高级语言编写的,主要是 solidity。智能合约被编译成操作码,这些操作码可在专为以太坊构建的虚拟机上执行,即EVM。EVM 具有可移植性和健壮性,因为 EVM 在运行时执行检查以防止崩溃。尽管这些检查确实会带来性能损失。

由于以太坊合约可以用四种语言之一编写:solidity、serpent、LLL 和 Mutan,因此有四个编译器将这四种语言编写的智能合约转换为在 EVM 上运行的操作码。另一个相关概念是以太坊客户端,它指的是安装在节点上的一组软件,用于解析和验证区块链交易、智能合约以及所有相关内容。以太坊客户端采用八种语言之一实现:Python、C++、Go、JavaScript、Java、Haskell、Ruby 和 Rust。已实现的 EVM 是以太坊客户端的重要组成部分。因此,操作码可以在八个客户端实现之一上运行。EVM 最初是为货币交易而设计的,后来扩展到其他数字资产。因此,支持某些功能存在限制。开发人员面临一些严格的限制(例如,字符串或本地寄存器的使用)。

以太坊 gas

以太坊交易可以调用智能合约,智能合约可以反过来调用另一个智能合约,然后又调用另一个,依此类推。当智能合约存在缺陷时,可能会导致无限循环。在区块链之外,很容易解决无限循环的问题。可以通过简单关闭服务器、重新启动服务器、调试程序、修复代码中的错误逻辑、重新编译和重新部署来停止失控程序。

在以太坊区块链上,这种方法根本行不通!想象一下,如果全球范围内数万个节点几乎同时进入无限循环。为了阻止无限循环的智能合约,所有节点都需要在短时间窗口内关闭。只要一个节点未能遵守,无限循环的智能合约仍将处于活动状态并运行。协调和关闭所有这些节点是一场后勤噩梦。

为了解决这个问题,引入了gas的概念。一辆车依靠燃烧汽油的发动机来运行。当发动机用完汽油时,车辆就会停止。以太坊引入了 gas 的概念来实现相同的效果。当向以太坊区块链提交交易时,请求者需要提供最大 gas 数量。例如,在下面的例子中,提交了一个调用 HelloWorld 智能合约的交易请求,其最大消耗不超过指定的 gas 值:

当此请求被挖矿节点验证时,将调用 HelloWorld 智能合约。在 EVM 上运行的每个操作都会消耗预定义数量的 gas。例如,ADD(求和操作)消耗三个 gas,而 MUL(乘法操作)则使用五个 gas。为了说明问题,假设一个智能合约写得很糟糕,并且包含一个无限循环。此外,我们假设每个循环由一个 ADD 操作和一个 MUL 操作组成。因此,一个循环将消耗八个 gas(三个 gas 用于 ADD,五个 gas 用于 MUL)。在 EVM 执行足够多的循环后,将消耗指定的最大 gas 值。因此,EVM 停止执行合约。因此,所有节点将在大约相同的时间停止运行。gas 的另一个优点是使垃圾邮件攻击的成本昂贵化,从而降低了黑客风险。

Gas 是一种用于测量消耗的计量单位,就像千瓦是用于测量电力使用的单位一样。假设一个家庭在一个月内使用了 210 KW。在向家庭发送账单之前,公用事业公司首先根据预先定义的转换率将 210 KW 转换为美元。假设 1 千瓦的价格为 0.2 美元,那么一个月的总费用为 0.2 * 210 = 42 美元。类似地,燃气使用量被转换为以太并收取给请求者。以太坊允许请求者在提交交易时指定转换率。矿工有权选择性地处理交易,优先处理费率较高的交易。如果请求者未指定费率,则 EVM 使用默认费率,这个费率会有所不同。例如,2016 年的费率是 1 gas = 0.00001 ETH。到 2018 年,一个 gas = 0.00000002 ETH。

账户

第十二章 区块链技术导论中,我们讨论了地址,这是一个类似账户的概念,用于承载比特币的余额。比特币使用 UTOX 模型来管理地址之间比特币的转移。然而,要找到地址的余额,必须检索整个账本,这非常不方便。这种不便之处在于比特币不支持链上图灵完备编程语言,也没有状态的概念。另一方面,以太坊区块链支持脚本语言和智能合约;它可以维护状态。以太坊交易通过调用智能合约方法来管理状态转换。以太坊不再需要依赖 UTOX 来管理支付。相反,它通过状态转换使用账户和余额进行操作。状态表示所有账户的当前余额,以及其他数据。状态不存储在区块链上。它在离线保存在 Merkle Patricia 树中。这是因为状态是可变数据,而区块是不可变的。与比特币一样,加密货币钱包可用于管理公钥和私钥*或账户,用于接收或发送 ETH。换句话说,以太坊引入了账户的概念。

以太坊支持两种类型的账户:外部拥有的账户(由人类用户通过拥有私钥控制)和合约账户。

  • 外部可控制的账户

    • 具有以太币余额

    • 可以发起转移以太币或触发智能合约代码的交易

    • 由用户通过私钥控制

    • 没有关联的智能合约代码

  • 合约账户

    • 具有以太币余额

    • 具有关联的智能合约代码

    • 通过从其他合约接收的交易或调用触发智能合约代码的执行

  • 对于这两种类型的账户,有四个组件

    • nonce:对于外部拥有的账户,它指的是从该账户地址发送的交易数量;对于合约账户,nonce 每次调用另一个合约时增加

    • balance:这是该地址拥有的 Wei 的数量

    • storageRoot:账户存储内容的 256 位哈希

    • codeHash:该账户的代码的哈希是 EVM;这是当地址接收到调用时执行的代码

当从合约账户向外部拥有的账户转移以太币时,会收取费用,例如 21,000 gas。当从外部拥有的账户发送以太币到合约账户时,费用较高,并取决于交易中发送的智能合约代码和数据。

以太坊地址具有以下格式:

  1. 以 0x 为前缀开始,这是十六进制的常见标识符

  2. ECDSA 公钥的 Keccak-256 哈希的右侧 20 字节(大端序)

由于在十六进制中,两个数字被存储在一个字节中,一个 20 字节的地址用 40 个十六进制数字表示。一个示例地址是0xe99356bde974bbe08721d77712168fa074279267

预言机

正如我们已经了解的,相同的以太坊智能合约在全球的节点上运行。但我们还没有强调的是,所有这些节点接受相同的输入,并应该产生相同的输出。这被称为确定性。以太坊依赖于这种确定性,因为为了验证智能合约和交易的有效性,挖矿节点必须在运行相同的代码和输入时产生相同的结果。

这种确定性产生了一个挑战。一方面,以太坊是一个通用的平台,可以用于转移任何数字或数字化资产。其智能合约需要来自外部来源的数据或输入,如互联网上的股价、宏观经济或微观经济指标等。如果不能访问这些信息来源,智能合约的用例将仅限于其潜力的一小部分。另一方面,即使有微小的时间差异,节点可能从外部来源获取不同的信息。有了不同的输入,节点最终会得到不同的输出。因此,确定性性质不成立。因此,智能合约不允许调用互联网 URL 或直接从外部来源获取数据。为了解决这一悖论,实施了预言机的概念。

根据韦氏词典,预言机的其中一个定义是神明透过神谕者揭示隐藏的知识或神圣的目的的神殿。在区块链世界中,预言机指的是提供外部数据的第三方或去中心化数据源服务。预言机提供了从现实世界到数字世界的接口。预言机数据不是区块链的一部分,它被保存在链下。

预言机有不同类型。其中两种是软件预言机和硬件预言机:

  • 软件预言机:通常指轻松获取的在线信息,如股指收盘价、外汇汇率、经济新闻或天气预报等。软件预言机很有用,因为它们为智能合约提供了各种类型和最新信息。

  • 硬件预言机:通常指扫描信息,如 UPS 交付扫描、挂号邮件扫描或供应商货物交付扫描。这种信息对于激活在事件发生时触发的智能合约是有用的。

其他概念

由于以太坊是建立在比特币之上的,许多基本概念已经在第十二章中讨论过,区块链技术简介。在本小节的其余部分,我们将简要介绍其中一些,并重点关注关键区别。

  • 共识算法

    • 与比特币一样,PoW 是其共识算法。与比特币不同,以太坊正在计划切换到另一种称为股权证明PoS)的共识算法,以在下一个版本的 serenity 中显著提高性能。
  • 私有区块链

    • 总的来说,比特币和以太坊都是公有区块链,因为网络对任何人开放,节点可以自由加入。

    • 以太坊存在私有链的变体。在私有以太坊中,节点需要在加入网络之前获得批准。这些区块链称为私有区块链。私有区块链适用于企业应用。超级账本和 JPM 摩根的 Quorum 是以太坊私有区块链的著名变体的示例。另一个示例是 Brainbot 的 hydrachain。

  • 链下数据

    • 在比特币区块链中,我们不太谈论链下数据的概念。在以太坊区块链中,需要讨论这个话题。存在多种情况,数据无法存储在链上:

      • 第一种情况是状态变量。在区块链中存储的所有数据都是不可变的,因为区块的内容被哈希,而区块通过这些哈希链接在一起。区块的内容发生微小变化将导致之后所有区块的重构,这显然是不可行的。然而,状态变量例如用于保存余额。它们会改变内容以反映余额变化。解决方案是将它们保存在链下。

      • 神谕是另一个例子,从外部来源提取的信息保存在链下,以供智能合约使用。

      • 以太坊的发明是为了允许交易通用数字或数字化资产。描述底层资产的元数据保存在链下。

      • 对于比特币,分布式分类账必须保存在所有节点上,以提供交易验证所需的信息。在以太坊的情况下,加密货币或数字资产的余额可以直接从状态变量中检索。无需浏览分类账即可获取余额以确定发送方地址是否有足够的资金。因此,完整节点可以选择仅保留分类账的一部分,即裁剪分类账。被裁剪的区块可以在链下的集中位置保存以供将来查询。

  • 测试

    • 彻底测试、反复测试和三重测试智能合约至关重要。安全测试至关重要。正如前面所述,在以太坊短暂的历史中,曾发生过几起备受关注的黑客事件,主要是由于有漏洞的智能合约代码。

    • 由于智能合约中引入的错误,以太坊比比特币不安全。以太坊智能合约保存在链式区块中,且未加密。黑客可以轻易发现并探索有漏洞的合约代码的脆弱性,并进行攻击。另一方面,像比特币一样,以太坊上的数据和交易相对安全,不容易受到黑客攻击。只有合约是黑客可以构造恶意交易来调用和滥用的。

    • 智能合约部署后,将会永久不变。部署修订后的代码将成为一个具有不同地址的新合约。它具有具有新余额的不同状态变量。

    • 智能合约的部署不是免费的。它会消耗 gas。

  • 数字签名、加密和公钥/私钥

    • 比特币是一个多签名过程。为了执行交易,双方都必须签署它。以太坊类似。此外,智能合约的部署也需要数字签名。

    • 像比特币一样,使用以太坊区块链可以同时生成一对公钥和私钥的钱包应用程序。地址由公钥派生;也就是说,地址只是公钥的哈希值。发送者使用私钥签署交易,接收者使用公钥验证签名的真实性。通常,一对公钥和私钥可以用于支持以下两种类型的活动:

    • 发送秘密消息:公钥用于加密消息,私钥用于解密消息。

    • 签名:使用私钥进行加密并生成签名。 公钥用于解密以进行签名验证。

    • 目前比特币和以太坊的区块交易内容均未加密。另一方面,Zcash 的区块内容是加密的。

    • 由于每个以太坊交易,包括智能合约,都必须经过数字签名,一个节点只需要接受数字签名的请求,可能无需验证整个交易历史。这种方法可以帮助提高性能。

  • DAO

    • DAO 指的是分散自治组织。不应将其与名为 The DAO 的组织混淆,后者与一次黑客事件密切相关,导致了 以太坊 分裂为 以太坊 (ETH) 和 以太坊经典 (ETC)。

    • DAO 可以被认为由智能合约组成,这是一种构建于分散代码之上的层次结构,即分散核心 → 智能合约 → DAO。

      • 分散的代码保存在多个节点上。它肯定会运行且无法停止。

      • 智能合约转移货币和数字资产。

      • DAO 由智能合约组成并创建独立实体或社区。

  • DApp

    • DApp 是一个重要的话题。由于书籍大小的限制,我们只简要提及它:

      • DApp 指的是分散应用程序,并使用分散代码。

      • 以太坊是一个通用的 DApp 平台。

      • 一个以太坊 DApp,像任何其他区块链 DApp 一样,具有去中心化的后端(例如,智能合约)和集中化的前端(用于与区块链交互的客户端应用程序)。这种架构是由今天区块链的性能和限制的原因所决定。

      • 前面讨论过,大部分后端、数据库和业务逻辑都是托管在链外的。

  • 以太坊问题

    • 以太坊受到从比特币继承而来的问题的影响:

      • 数据可能因分叉而丢失。当存在两个竞争的链时,无法快速增长的链必须被丢弃,以保持所有节点上的数据一致性。如果交易未被包含在获胜链的区块中,那些在短链上的交易将会丢失,甚至被原始请求者所不知晓!

      • 由于链上数据并未加密,区块链不具备匿名性和保密性。

      • 地址未经验证。这很糟糕。如果接收者地址被错误输入,那么转移给它的硬币将是永久的,因为交易是永久的,硬币将永远锁定!

      • PoW 算法消耗大量电力。据报道,中国一些大型挖矿操作需要专门的发电站供电。

性能

从比特币继承而来的另一个问题是以太坊运行速度缓慢。它比其他承载交易数据的平台慢得多,例如传统数据库。例如,比特币平均需要 10 分钟建立一个新的记录。按照一个经验法则,等待六个新区块建立完毕后,一笔交易就会被视为完成(就像在数据库中的确认一样)。这意味着,在平均情况下,请求者将等待一个小时才能看到请求完成。在以太坊中,矿工建立区块的平均时间是 17 秒,建议在交易确认前等待 12 个区块。这是 12 * 17 = 204 秒,即用户等待 3.4 分钟的时间。在这里,在确认交易前等待一些连续的区块建立是有用的。在任何时候,以太坊都可能存在竞争链。等待给予以太坊充足的时间来处理竞争链问题并达成共识。

吞吐量

吞吐量是衡量系统在一定时间窗口内可以处理多少信息单位的指标。为了衡量交易平台的性能,吞吐量用TPS表示,即每秒交易数量:

  • 对于比特币,TPS 可以计算如下。比特币区块通常包含 1500-2000 笔交易。我们使用最高数值 2000。由于确认这 2000 笔交易需要 60 分钟,所以其 TPS = 2,000 / (60*60) = 0.56;也就是说,每秒仅半个交易。对以太坊进行类似的计算,得到 TPS = 2,000 / 204 = 9.8,几乎是 10 笔交易每秒——比比特币要好得多。以太坊基金会正在采用夏丁(sharding)方法,由维塔利克(Vitalik)领导,旨在将 TPS 提高 80 倍。

  • 以 VISA 为例,平均 TPS 为 2000,峰值为 40,000。像 VoltDB 这样的高性能数据库可以处理每秒超过一百万次插入。证券交易所可以匹配成千上万笔交易。

  • 然而,这种比较并不完整。从商业角度来看,只有在清算和结算时,信用卡或交易才最终确定。对于信用卡,账单周期通常为 2-3 个月。证券交易所需要三天来结算一笔交易。从这个意义上讲,以太坊要快得多,因为在区块链上交易和结算是同时进行的。

  • 与数据库相比,以太坊处于不利地位。数据库提交可以在插入、更新或删除事务后立即进行。

  • 这些是导致以太坊速度变慢的原因:

    • 每个完整节点必须执行相同的智能合约代码。

    • 随着以太坊网络规模的增长,达成共识所需的时间将变长,因为在越来越多的节点之间传输数据来验证交易、访问信息和通信需要时间。

  • 有方法可以增加吞吐量。以下是一些:

    • 当区块大小增加时,可以在一个区块中托管更多的交易,并且可以获得更高的 TPS。

    • 并行运行多个链。企业链,如超级账本(Hyperledger Fabric)和 R3 的 Corda 使用这种方法。

    • 状态通道设计有助于提高吞吐量。以太坊的状态通道实现示例是雷电网络(Raiden)。微型雷电网络于 2017 年 11 月推出。状态通道背后的想法是在两个参与方之间使用离链进行交易,并在链上进行交易结算。离链交易是另一个值得深入讨论的话题,但不在本书中。

股权证明(PoS)

PoS 共识算法基于这样一个原则,即当一个矿工拥有更多的硬币时,该矿工具有更多的权力来挖掘或验证交易,建立新区块的机会更高,因此获得更多奖励硬币的机会也更高。PoS 是节能的,并且可以更快地达成共识。

有几种随机化方法可用于选择构建下一个区块的矿工,而不仅仅是基于外部拥有账户的以太坊余额,以避免最富有的矿工始终被选择的情况:

  • 随机区块选择*:* 使用一个公式来寻找组合中最低的哈希值以及选择矿工的股份大小。

  • 货币年龄选择*:* 拥有足够长时间(比如 30 天)的货币有资格竞争下一个区块。拥有更老和更大一组货币的矿工有更好的机会被授予该角色。

  • 委托股权证明*:* 该实现选择了一定数量的节点来提出和验证区块到区块链。

  • 随机化股权证明*:* 每个节点都是随机选择的,使用可验证的随机信标来构建新区块。

以太坊正在努力在一个新的标记版本中用 PoS 替换 PoW。

Casper

PoS 正在被作为对计算效率低下的 PoW 算法的替代而进行研究。由于对出现一组集中化超级节点(在构建新区块方面起到过大作用)等问题的担忧,PoS 尚未完全在主网上实施和升级。Casper 是以太坊社区努力实现从 PoW 到 PoS 的过渡。

在 Per Casper 协议中,验证者(比特币中的矿工的以太坊等效物)将部分 Ether 作为赌注放在一边。当验证者确定要构建的候选区块时,验证者将在该区块上下赌注。如果该区块确实添加到链上,验证者将根据其赌注的大小获得奖励。行为恶意的验证者将被惩罚,其赌注将被移除。Casper 有两个主要项目:Casper FFG 和 Casper CCB。

Casper FFG(友好最终性小工具; Vitalik 版本的 Casper)是一个混合算法,运行在 PoW 上,但将网络上每 50^(th) 个区块视为 PoS 检查点。验证者对这些区块的最终性进行投票,并将其写入区块链。FFG 旨在成为向完全采用 PoS 过渡的中间步骤。FFG 已经在测试网络上运行。它将很快完全实现在主网上。

Casper CBCCorrect by Construction,Vlad's Casper)更加激动人心。CBC 专注于设计协议,其中一个节点对安全性的局部视图可以扩展到达成共识安全。到目前为止,该方法仅仅是研究,并且没有发布计划可供其进入以太坊。

Plasma

2017 年,Buterin 和 Joseph Poon 提出了他们的想法,呼吁扩展以太坊的性能,即增加 TPS。与状态通道设计类似,Plasma 是一种在链下进行交易的技术,同时依赖底层以太坊区块链提供安全性。因此,Plasma 属于 链下 技术的一部分。Truebit 是该组中的另一个例子。

Plasma 的工作方式如下:

  • 智能合约是在主链上创建的,并被视为 Plasma 子链的根。它们定义了子链的规则,并被调用来在主链和子链之间移动资产。

  • 创建具有自己共识算法的子链,例如 PoS。

  • 部署智能合约,定义实际的业务规则,到子链。

  • 在主链上创建的数字资产通过调用 plasma 根合约转移到子链上。

  • 子链上的区块构建者定期向主链提交验证,证明子链的当前状态符合共识规则。用户发送和执行请求,而不需要直接与主链交互。

Plasma 具有以下优势:

  • 允许以太坊区块链处理更大的数据集

  • 启用更复杂的应用在区块链上运行

  • 大大增加吞吐量

以太坊社区正在积极开发以太坊 plasma. Plasma-MVP(最小可行产品)首先正在进行开发,以积累经验并测试其可行性。有可能在 2018 年底发布 plasma-mvp。Plasma 的发布将在一个或多个季度内进行。

分片

Vitalik 最初提出了用于扩展以太坊区块链的分片概念。他的提案是把区块链分割成数百或数千个独立的碎片:分片。所有分片共享相同的共识算法和安全模型。这些分片将不处理不同类型的任务,并且不需要所有全节点进行验证。相反,每个分片都用于单一目的,因此在该目的上非常高效。总之,分片将网络状态分割为多个分片,每个分片都有自己的交易历史和网络状态的一部分。为了在区块链上实现分片概念,需要一个验证者管理合约,这是一个智能合约。它验证每个分片的区块头,维护验证者的利益,并在分片之间伪随机地选择验证者。分片提供了一种替代方式,可以戏剧性地提高以太坊的性能,并且可能在 2020 年早期实施。

摘要

以太坊是在比特币的基础上开发出来的,引入了智能合约以及像 solidity 这样的图灵完备脚本语言。以太坊是一个面向 DApp 开发的通用平台。该平台非常受欢迎。然而,以太坊还不够成熟。与比特币相比,它更容易受到黑客攻击,因为编写智能合约时的任何人为错误都会被所有人看到。它从比特币那里继承了性能问题。目前有许多倡议正在解决这个可伸缩性问题。在接下来的章节中,我们将深入了解 solidity,这是编写以太坊智能合约的最流行语言。

第十四章:Solidity 编程概述

Solidity 是一种智能合约编程语言。它由 Gavin Wood、Christian Reitwiessner、Alex Beregszaszi 和几位以太坊核心贡献者开发。它是一种类似 JavaScript 的通用语言,旨在针对 以太坊虚拟机EVM)。Solidity 是以太坊协议中的四种语言之一,与 Serpent(类似于 Python)、LLL类 Lisp 语言)、Vyper(实验性)和 Mutan(已弃用)处于同一抽象层级。社区逐渐趋同于 Solidity。通常,今天任何人谈论以太坊的智能合约时,都隐含地指的是 Solidity。

在本章中,我们将讨论以下主题:

  • 什么是 Solidity?

  • Solidity 开发环境工具

  • 智能合约简介

  • 常见智能合约模式

  • 智能合约安全

  • 案例研究 – 众筹活动

什么是 Solidity?

Solidity 是一种静态类型的合约语言,包含状态变量、函数和常见数据类型。开发者可以编写实现智能合约中业务逻辑函数的去中心化应用程序(DApps)。合约在编译时验证和强制约束,而不是在运行时。Solidity 被编译成 EVM 可执行字节码。一旦编译完成,合约被上传到以太坊网络。区块链将为智能合约分配一个地址。区块链网络上的任何有权限的用户都可以调用合约函数来执行智能合约。

下面是一个典型的流程图,展示了从编写合约代码到在以太坊网络上部署和运行的过程:

Solidity 开发环境工具

智能合约开发仍处于起步阶段。创建这样的合约并以方便的方式与之交互可以通过多种方式实现。以下强大的工具可用于在以太坊平台上构建、监视和部署您的智能合约开发。

基于浏览器的集成开发环境(IDE)

在这个部分,我们将看看像 Remix 和 EthFiddle 这样的在线浏览器工具。

Remix

Remix 是一个强大的、开源的智能合约工具,可以帮助你直接从浏览器中编写 Solidity 代码。它支持编译、运行、分析、测试和调试选项。在开发和测试时,Remix 提供了以下三种环境:

  • JavaScript VM:Remix 自带五个以太坊账户,每个账户默认存入 100 以太币。这对于在开发阶段测试智能合约非常方便。不需要进行挖矿,因为这是自动完成的。当你是一个初学者时,这个选项是一个不错的选择。

  • 注入的 Web3:此选项将直接调用注入的浏览器 web3 实例,如 MetaMask,以太坊网络浏览器扩展。MetaMask 提供了许多功能和特性,就像普通的以太坊钱包一样,它允许您与 DApp 进行交互。

  • Web3 提供者:Remix 还支持 Web3 提供者。web3.js 库是官方的以太坊 JavaScript API。它用于与以太坊智能合约进行交互。您可以通过 web3j API 连接到区块链网络。Web3j 支持三种不同的提供者:HTTPProvider、WebsocketProvider 和 IpcProvider。在 Remix Web3 中,您可以提供 HTTP URL 来连接远程区块链实例。该 URL 可以指向您的本地私有区块链、测试网和其他实例端点。

首先使用 Remix solidity IDE:remix.ethereum.org。以下是 Remix 的 UI 截图:

EthFiddle

EthFiddle 是一个非常简单的 solidity 基于浏览器的开发工具。您可以快速测试和调试智能合约代码,并分享代码的永久链接。使 EthFiddle 突出的一个功能是其进行安全审计的潜力。以下截图显示了软件界面:

EthFiddle 软件界面

这是 EthFiddle solidity IDE 的链接:ethfiddle.com

命令行开发管理工具

命令行工具是服务器端以太坊工具,用于创建 DApp 项目的基本结构。

Truffle

Truffle 是一个流行的以太坊开发环境和测试框架,是以太坊的资产管道。Truffle 的主要功能包括以下内容:

  • 内置智能合约编译、连接、部署和二进制管理

  • 使用 Mocha 和 Chai 进行自动合约测试的 Truffle 网站链接:http

  • 可编写的部署和迁移框架

  • 部署到多个公共和私人网络的网络管理

  • 用于直接与合同通信的交互式控制台

  • 我们将在下一章节中进行更详细的讨论,并使用 Truffle 开发 ERC20 代币的 DApp。

  • 以下是 Truffle 的网站链接:truffleframework.com/

智能合约简介

让我们从最基本的智能合约示例HelloWorld.sol开始,如下所示:

pragma solidity ⁰.4.24;

contract HelloWorld {
  string public greeting;

  constructor() public {
    greeting = 'Hello World';
  }

  function setNewGreeting (string _newGreeting) public {
    greeting = _newGreeting;
  }
}

Solidity 的文件扩展名是.sol。它类似于 JavaScript 文件的.js,以及 HTML 模板的.html

solidity 源文件布局

一个 solidity 源文件通常由以下结构组成:pragma、注释和导入。

Pragma

第一行包含关键字 pragma 的源代码文件简单地指出该文件不会与版本早于 0.4.24 的编译器一起编译。任何新版本也不会破坏功能。符号 ^ 暗示另一个条件 - 源文件也不会适用于版本大于 0.5.0 的编译器。

注释

注释用于使源代码更容易让人类理解程序的功能。多行注释用于对代码进行大文本描述。编译器会忽略注释。多行注释以 /* 开始,以 */ 结束。

HelloWorld 示例中,对 setget 方法有注释:

  • 方法:setNewGreeting(string _newGreeting) {} 函数

  • @param:这用于指示将哪些参数传递给方法,以及它们预期的值是什么

导入

Solidity 中的 import 关键字与 JavaScript 的过去版本 ES6 非常相似。它用于将库和其他相关功能导入到您的 solidity 源文件中。Solidity 不支持 export 语句。

以下是一些导入示例:

import * as symbolName from “solidityFile”

上述行将创建一个名为 symbolName 的全局符号,其中包含从导入文件 solidityFile 中的全局符号成员。

另一个与前述导入相当的 solidity 特定语法是以下内容:

import solidityFile as symbolName;

您还可以导入多个符号,并将其中一些符号命名为别名,如下所示:

import {symbol1 as alias, symbol2} from " solidityFile";

以下是使用 zeppelin solidity 库创建 ERC20 代币的示例:

pragma solidity ⁰.4.15;
import 'zeppelin/contracts/math/SafeMath.sol';
….
contract ExampleCoin is ERC20 {
  //SafeMath symbol is from imported file SafeMath.sol'
  using SafeMath for uint256;
   …
}

对于上述代码片段中显示的示例,我们从 Zeppelin 导入了 SafeMath 库,并将其应用于 uint256

路径

当导入一个 solidity 文件时,文件路径遵循一些简单的语法规则:

  • 绝对路径:/folder1/ folder2/xxx.sol/ 开始,路径位置是从相同的 solidity 文件位置到导入的文件。在我们的 ERC 20 示例中,如下所示:
      import 'zeppelin/contracts/math/SafeMath.sol';

实际项目结构如下所示:

相对路径

../folder1/folder2/xxx.sol:这些路径是相对于当前文件的位置进行解释的,. 表示当前目录,.. 表示上级目录。

在 solidity 路径中,可以指定路径前缀重映射。例如,如果要导入 github.com/ethereum/dapp-bin/library,可以先将 GitHub 库克隆到 /usr/local/dapp-bin/library,然后运行如下所示的编译器命令:

solc github.com/ethereum/dapp-bin/library=/usr/local/dapp-bin/library

然后,在我们的 solidity 文件中,您可以使用以下 import 语句。它将重映射到 /usr/local/dapp-bin/library/stringUtils.sol

import "github.com/ethereum/dapp-bin/library/stringUtils.sol " as stringUtils;

编译器将从那里读取文件。

合约的结构

合约包括以下构造:状态变量,数据类型,函数,事件,修饰符,枚举,结构和映射。

状态变量

状态变量是永久存储在合约存储中的值,并用于维护合约的状态。

以下是代码示例:

contract SimpleStorage {
    uint storedData; // State variable
    //…
}

数据类型

Solidity 是一种静态类型语言。熟悉 JavaScript 和 Python 等语言的开发人员会发现 Solidity 语法很容易上手。每个变量都需要指定数据类型。永远传递的变量称为值类型,它是内置或预定义的数据类型。

Solidity 中的值类型如下:

类型 运算符 示例 注意
Bool !、&&、||、==、!= bool a = true; 布尔表达式为 true 或 false。
Int(int8 到 int256) 比较运算符:<=、<、==、!=、>=、>、位运算符:&、|、^、+、-、一元 -、一元 +、*、/、%、**、<<、>> int a = 1; 有符号整数,从 8 到 256 位,步长为 8。
Uint(uint8 到 uint256) 比较运算符:<=、<、==、!=、>=、>位运算符:&、|、^、+、-、一元 -、一元 +、*、/、%、**、<<、>> uint maxAge = 100; 无符号整数,从 8 到 256 位,步长为 8。
地址 ⇐、<、==、!=、>=、>
address owner = msg.sender;
address myAddress = 0xE0f5206B…437b9;
保存 20 字节值(以太坊地址的大小)。
<address>.balance address.balance() 地址成员,并返回地址的以太币余额。
<address>.transfer beneficiary.transfer(highestBid) 地址成员,并向地址发送以太币(以 Wei 为单位)。如果转账操作失败,则抛出异常,并撤销交易中的所有更改。
<address>.send msg.sender.send(amount) 地址成员,并向地址发送以太币(以 Wei 为单位)。如果发送操作失败,则返回 false。
<address>.call
someAddress.call.
value(1ether)
.gas(100000) ("register", "MyName")
另一个合约的执行代码,在失败的情况下返回 false,转发所有可用的 gas,可调节,当您需要控制要转发多少 gas 时应该使用。
<address>.delegatecall , _library.delegatecall(msg.data); 执行另一个合约的代码,但使用调用合约的状态(存储)。
固定大小的字节数组(bytes1、bytes2、...、bytes32) 比较运算符:<=、<、==、!=、>=、>、位运算符:&、|、^、~、<<、>>、获取数组数据:array[index] uint8[5] memory traits = [1,2,3,4,5]; 使用关键字 byteN 定义固定大小的字节数组,其中 N 是从 1 到 32 的任意数字,它限制了大小,会更便宜,也会节省 gas。
动态大小的数组 bytes string
/**bytes array **/
bytes32[] dynamicArray
function f() {
        bytes32[] storage storageArr = dynamicArray
        storageArr.length++;
}
/**string array **/
bytes32[] public names
Solidity 支持动态大小的字节数组和动态大小的 UTF-8 编码字符串。
十六进制字面量 hex"1AF34A" 十六进制字面量以关键字 hex 为前缀,并用单引号或双引号括起来。
地址字面量 0x5eD8Cee6b63b1c6AFce3AD7c92f4fD7E1B8fAd9F 它是经过地址校验和测试的十六进制字面量。
字符串字面量 "Hello" 字符串字面量通常用单引号或双引号写。

枚举类型

枚举是一种带有一组受限制的常量值的类型。以下是示例:

pragma solidity ⁰.4.24;
  contract ColorEnum {
    enum Color {RED,ORANGE,YELLOW, GREEN}
    Color color;
    function construct() public {
     color = Color.RED;
    }
    function setColor(uint _value) public {
      color = Color(_value);
    }
  function getColor() public view returns (uint){
      return uint(color);
  }
}

结构体类型

结构体是一种包含命名字段的类型。可以使用结构体声明新类型。以下是代码示例:

struct person {
         uint age;
         string fName;
         string lName;
         string email;
    }

映射

映射充当由键类型和相应值类型对组成的哈希表。以下是示例:

pragma solidity ⁰.4.24;
contract StudentScore {
    struct Student {
        uint score;
        string name;
    }
    mapping (address => Student) studtents;
    address[] public studentAccts;
    function setStudent(address _address, uint _score, string _name) public {
        Student storage studtent = studtents[_address];
        studtent.score = _score;
        studtent.name = _name;
        studentAccts.push(_address) -1;
    }
    function getStudents() view public returns(address[]) {
        return studentAccts;
    }
    function getStudent(address _address) view public returns (uint, string) {
        return (studtents[_address].score, studtents[_address].name);
    }
    function countStudents() view public returns (uint) {
        return studentAccts.length;
    }
}

函数

函数是合同内代码的可执行单元。以下是 Solidity 中函数的结构:

function (<Input parameters>) {access modifiers} [pure|constant|view|payable] [returns (<return types>)]

输入参数

函数可以传递输入参数。输入参数的声明方式与变量相同。

在上一个HelloWorld示例中,我们使用输入参数string _newGreeting定义了setNewGreeting。以下是此步骤的示例:

function setNewGreeting (string _newGreeting) {
  greeting = _newGreeting;
}

访问修饰符

Solidity 访问修饰符用于在 Solidity 中提供访问控制。

Solidity 中有四种类型的访问修饰符,列举如下:

  • Public:可以从此合同、继承的合同和外部访问

  • Private:只能从此合同访问

  • Internal:只能从此合同和继承自它的合同访问

  • External:无法在内部访问,仅可外部访问

输出参数

输出参数可以在return关键字之后声明,如下所示的代码片段:

function getColor() public view returns (uint){
      return uint(color);
  }

在 Solidity 中,pure函数是承诺不修改或读取状态的函数。

pure|constant|view|payable

如果函数修饰符定义为 view,则表示该函数不会更改存储状态。

如果函数修饰符定义为 pure,则表示该函数不会读取存储状态。

如果函数修饰符定义为 constant,则表示该函数不会修改合同存储。

如果函数修饰符定义为 payable,则修饰符可以接收资金。

    uint amount =0;
    function buy() public payable{
        amount += msg.value;
    }

在上面的示例中,buy函数具有可支付修饰符,这确保您可以向buy函数发送以太。没有任何名称,并用可支付关键字注释的函数称为可支付回退函数。

pragma solidity ⁰.4.24;
// this is a contract, which keeps all Ether to it with not way of
// retrieving it.
contract MyContract {
    function() public payable { }
}

修饰符

在 Solidity 中,修饰符用于更改函数的行为。它们可以在执行函数之前自动检查条件。以下是示例:

pragma solidity ⁰.4.24;
contract Modifiers {
         address public admin;
    function construct () public {
       admin = msg.sender;
    }
    //define the modifiers
    modifier onlyAdmin() {
        // if a condition is not met then throw an exception
        if (msg.sender != admin) revert();
        // or else just continue executing the function
        _;
    }
    // apply modifiers

    function kill() onlyAdmin public {
         selfdestruct(admin);
    }
}

事件

事件用于跟踪发送到合同的交易的执行情况。有与 EVM 日志记录设施方便的接口。以下是示例:

pragma solidity ⁰.4.24;
contract Purchase {
    event buyEvent(address bidder, uint amount); // Event
    function buy() public payable {
        emit buyEvent(msg.sender, msg.value); // Triggering event
    }
}

构造函数

构造函数是创建和初始化合同的特殊方法。在 Solidity v0.4.23 中,Solidity 引入了这种新的构造函数表示法,旧的构造函数已被弃用。

//new
pragma solidity ⁰.4.24;
contract HelloWorld {
  function constructor() public {
    // ...
  }
}
//deprecated
pragma solidity ⁰.4.22;
contract HelloWorld {
  function HelloWorld () public {
    // ...
  }
}

常量状态变量、单位和函数

常量的值不能通过重新分配来更改,并且在编译时间后不能重新声明。在 solidity 中,状态变量可以声明为常量。它不允许重新分配到区块链数据(例如,this.balanceblock.blockhash)或执行数据(tx.gasprice),或调用外部合约。

下表列出了 solidity 全局变量及其内建函数:

全局变量 / 函数 描述
msg.sender(address) msg.sender 是当前与合约调用消息交互的地址。
msg.data(bytes) msg.data 是当前与合约进行交互的地址的完整调用。数据以字节形式呈现。
msg.value(unit) msg.value 是当前与合约交互的地址,根据合约发送的消息的 Wei 数量。
msg.sig msg.sig 是当前与合约交互的地址,返回调用数据的前四个字节。
gasleft() returns (uint256) API 用于检查剩余的燃气。
tx.origin API 用于检查交易的发送方。
tx.gasprice API 用于检查交易的燃气价格。
now 获取当前的 Unix 时间戳。
block.number API 用于检索当前区块编号。
block.difficulty API 用于检索当前区块的难度。
block.blockhash(uint blockNumber) returns (bytes32) API 用于获取给定区块的哈希;结果仅返回最近的 256 个区块。
block.gasLimit(unit) API 用于获取当前区块的燃气限制。
block.coinbase () 返回当前区块矿工的地址。
keccak256(...); 返回(bytes32):计算(紧密打包的)参数的 Ethereum-SHA-3(Keccak-256)哈希。
sha3(...) 返回(bytes32):keccak256 的别名。
assert(bool condition) assert 用于检查条件。它表示在任何情况下都不应该为假的东西。此外,assert 使用 0xfe 操作码来引发错误条件。
require(bool condition) 应该使用 require 函数来确保有效的条件。当用户输入不当的内容时,它可能返回 false。此外,require() 使用 0xfd 操作码来引发错误条件。
revert() revert 仍然会撤消所有状态更改。
<address>.balance 它检查地址的 Wei 余额(uint256)。
<address>.send(uint256 amount) returns (bool) API 将 Wei 量发送到地址,并在失败时返回 false。
<address>.transfer(uint256 amount) API 将 Wei 量转移到地址,并在转移失败时抛出错误。
this 当前合约,显式转换为地址。
super 继承层次结构中更高一级的合约。
selfdestruct(address recipient) self-destruct 将销毁当前合约,并从以太坊的世界状态中删除与之相关的存储。
suicide(address recipient) self-destruct 的别名。

以太单位

Solidity 中的以太可分为 Wei、Kwei、Mwei、Gwei、Szabo、Finney、Kether、Mether、Gether 和 Tether。以下是转换单位:

  • 1 ether = 1,000 Finney

  • 1 Finney = 1,000 Szabo

  • 1 Szabo = 1,000 Mwei

  • 1 Mwei = 1,000 Kwei

  • 1 Kwei = 1,000 Wei

时间单位

一个 Solidity 时间单位可分为秒、分钟、小时、天、周和年。以下是转换单位:

  • 1 = 1 秒

  • 1 分钟 = 60 秒

  • 1 小时 = 60 分钟

  • 1 天 = 24 小时

  • 1 周 = 7 天

  • 1 年 = 365 天

继承、抽象和接口

许多最广泛使用的编程语言(如 C++、Java、Go 和 Python 等)支持面向对象编程OOP),并支持继承、封装、抽象和多态。继承使代码重用和可扩展性成为可能。Solidity 支持通过复制代码的方式实现多重继承,其中包括多态性。即使合约从多个其他合约继承,也只会在区块链上创建一个单一合约。

在 Solidity 中,继承与经典的面向对象编程语言非常相似。以下是一些示例:

pragma solidity ⁰.4.24;
contract Animal {
    constructor() public {
    }
    function name() public returns (string) {
        return  "Animal";
    }
    function color() public returns (string);
}
contract Mammal is Animal {
    int size;
    constructor() public {
    }
    function name() public returns (string) {
        return  "Mammal";
    }
    function run() public pure returns (int) {
        return 10;
    }
    function color() public returns (string);
}
contract Dog is Mammal {
     function name() public returns (string) {
        return  "Dog";
    } 
    function color() public returns (string) {
        return "black";
    }
}

Dog 从 Mammal 继承,其父合约是 Animal。调用 Dog.run() 时,将调用其父方法 run() 并返回十。调用 name,Dog.name() 将覆盖其父方法并返回 Dog 的输出。

在 Solidity 中,没有主体(无实现)的方法称为抽象方法。包含抽象方法的合约无法实例化,但可以用作基类。

如果一个合约从抽象合约继承,那么合约必须实现抽象父类的所有抽象方法,或者它也必须声明为抽象。

Dog 有一个具体的 color() 方法,这是一个具体的合约,可以编译,但父合约—mammal,和祖父合约—animal,仍然是抽象合约。

Solidity 中的接口类似于抽象合约;它们隐式地是抽象的,不能有实现。抽象合约可以具有实例方法,实现默认行为。接口中有更多的限制,如下所示:

  • 不能继承其他合约或接口

  • 不能定义构造函数

  • 不能定义变量

  • 不能定义结构体

  • 不能定义枚举

pragma solidity ⁰.4.24;
//interface
contract A {
    function doSomething() public returns (string);
}
//contract implements interface A
contract B is A {
    function doSomething() public returns (string) {
        return "Hello";
    }
}

在前面的示例中,合约是一个接口,contract B 实现了 interface A,并且有一个具体的 doSomething() 方法。

常见的智能合约模式

在本节中,我们将讨论智能合约编程语言的一些常见设计和编程模式。

访问限制

限制访问是一种 solidity 安全模式。它只允许授权方访问特定功能。由于区块链的公开性质,区块链上的所有数据对任何人都是可见的。关键是声明你的合约函数、受限访问控制的状态,并提供对智能合约功能的未经授权访问的安全性。

pragma solidity ⁰.4.24;
contract Ownable {
 address owner;
 uint public initTime = now;
 constructor() public {
 owner = msg.sender;
 }
 //check if the caller is the owner of the contract
 modifier onlyOwner {
 require(msg.sender == owner,"Only Owner Allowed." );
 _;
 }
 //change the owner of the contract
 //@param _newOwner the address of the new owner of the contract.
 function changeOwner(address _newOwner) public onlyOwner {
 owner = _newOwner;
 }
 function getOwner() internal constant returns (address) {
 return owner;
 }
 modifier onlyAfter(uint _time) {
 require(now >= _time,"Function called too early.");
 _;
 }
 modifier costs(uint _amount) {
 require(msg.value >= _amount,"Not enough Ether provided." );
 _;
 if (msg.value > _amount)
 msg.sender.transfer(msg.value - _amount);
 }
}
contract SampleContarct is Ownable {

 mapping(bytes32 => uint) myStorage;
 constructor() public {
 }
 function getValue(bytes32 record) constant public returns (uint) {
 return myStorage[record];
 }
 function setValue(bytes32 record, uint value) public onlyOwner {
 myStorage[record] = value;
 }
 function forceOwnerChange(address _newOwner) public payable
onlyOwner onlyAfter(initTime + 2 weeks) costs(50 ether) {
 owner =_newOwner;
 initTime = now;
 }
}

上述示例展示了访问限制模式应用于合约。我们首先定义了一个名为Ownable的父类,其中包含onlyOwnerchangeOwneronlyAfter函数修饰符。其他合约可以继承自此合约以使用定义的访问限制。SampleContract继承自Ownable合约,因此只有所有者可以访问setValue函数。此外,forceOwnerChange只能在合同创建后两周内以 50 以太币的成本进行调用,只有所有者才有权限执行该功能。

状态机

状态机是一种行为设计模式。它允许合约在其内部状态改变时改变其行为。智能合约函数调用通常会将合约状态从一个阶段移动到下一个阶段。状态机的基本操作包括两个部分:

  • 它遍历一系列状态,其中下一个状态由当前状态和输入条件决定。

  • 它根据状态迁移提供输出序列。

为了说明这一点,让我们开发一个简单的状态机。我们以洗碗为例。通常的过程是擦洗,冲洗,晾干,擦洗,冲洗,晾干。我们将状态机阶段定义为一个枚举类型。由于这是一个广泛的用例,此处仅介绍了与状态机相关的代码。省略了任何详细动作实现的逻辑,如rinse()dry()等。请参见以下示例:

pragma solidity ⁰.4.24;
contract StateMachine { 
 enum Stages {
 INIT,
 SCRUB,
 RINSE,
 DRY,
 CLEANUP
 }

 Stages public stage = Stages.INIT;
 modifier atStage(Stages _stage) {
 require(stage == _stage);
 _;
 }
 function nextStage() internal {
 stage = Stages(uint(stage) + 1);
 } 
 modifier transitionNext() {
 _;
 nextStage();
 }

 function scrub() public atStage(Stages.INIT) transitionNext {
 // Implement scrub logic here
 }

 function rinse() public atStage(Stages.SCRUB) transitionNext {
 // Implement rinse logic here
 }

 function dry() public atStage(Stages.SCRUB) transitionNext {
 // Implement dry logic here
 }

 function cleanup() public view atStage(Stages.CLEANUP) {
 // Implement dishes cleanup
 }
}

我们定义了函数修饰符atStage来检查当前状态是否允许该阶段运行函数。此外,transitionNext修饰符将调用内部方法nextStage()来将状态移动到下一个阶段。

智能合约安全

一旦智能合约部署在以太坊网络上,它就是不可变的,并且对所有人都是公开的。许多智能合约功能与账户支付相关;因此,在部署到主网络之前,安全性和测试对合约都至关重要。以下是有助于您更好地设计和编写无瑕疵以太坊智能合约的安全实践。

保持合约简单和模块化

尽量保持您的智能合约小、简单和模块化。复杂的代码很难阅读、理解和调试,并且容易出错。

在可能的情况下,使用写好的库工具。

限制本地变量的数量。

将不相关功能移动到其他合约或库中。

使用检查-效果-交互模式

与其他外部合约交互时要非常小心,这应该是你的函数中的最后一步。它可能引入几个意外的风险或错误。外部调用可能执行恶意代码。如果可能的话,应该考虑这些调用作为潜在的安全风险,并尽量避免。

pragma solidity ⁰.4.24;
// THIS CONTRACT is INSECURE - DO NOT USE
contract Fund {
    mapping(address => uint) userBalances;
function withdrawBalance() public {
     //external call
         if (msg.sender.call.value(userBalances[msg.sender])())
            userBalances[msg.sender] = 0;
}
}
contract Hacker {
    Fund f;
    uint public count;

    event LogWithdrawFallback(uint c, uint balance);

    function Attacker(address vulnerable) public {
        f = Fund(vulnerable);
    }
    function attack() public {
        f.withdrawBalance();
    }

    function () public payable {
        count++;
        emit LogWithdrawFallback(count, address(f).balance);
        if (count < 10) {
          f.withdrawBalance();
        }
    }
  }
}

msg.sender.call.value(userBalances[msg.sender])是一个外部调用,在调用withdrawBalance时,它将使用address.call.value()发送以太。黑客可以通过触发攻击回退函数来攻击资金合约,这将再次调用withdrawBalance方法。这将允许攻击者多次退款,耗尽账户中的所有以太。

上述合约漏洞称为递归调用。为了避免这种情况,您可以使用检查-效果-交互模式,如下面的示例所示:

pragma solidity ⁰.4.24;
contract Fund {
    mapping(address => uint) userBalances;
    funct
ion withdrawBalance() public {
         uint amt = userBalances[msg.sender];
         userBalances[msg.sender] =0;
         msg.sender.transfer(amt);
    }
}  

我们首先需要确定函数的哪一部分涉及到外部调用,uint amt = userBalances[msg.sender]; userBalances[msg.sender] =0;

该函数读取 userBalances 的值,并将其分配给一个本地变量,然后重置 userBalances。这些步骤是为了确保消息发送者只能向自己的帐户转账,但不能对状态变量进行任何更改。在实际转账到用户之前,用户的余额将减少。如果在转账过程中发生任何错误,整个交易将被撤销,包括状态变量中余额减少的转账金额。这种方法可以被描述为 乐观会计,因为在实际发生之前,效果已被写入完成。

用区块燃气限制的 DoS 攻击

以太坊区块链交易由于区块燃气限制只能处理一定数量的燃气,因此要小心观察是否有固定的限制集成。当一系列迭代的成本超出燃气限制时,交易将失败,并且合约可能会在某一点被停滞。在这种情况下,攻击者可能会攻击合约,并操纵燃气。

处理外部调用中的错误

正如我们之前讨论的,Solidity 有一些低级调用方法:address.call()address.callcode()address.delegatecall()address.send()。这些方法只在调用遇到异常时返回 false。因此,在合约中处理外部调用的错误非常重要,如下面的代码片段所示:

// good
if(!contractAddress.send(100)) {
    // handle error
}
contractAddress.send(20);//don't do this
contractAddress.call.value(55)(); // this is doubly dangerous, as it will forward all remaining gas and doesn't check for result
contractAddress.call.value(50)(bytes4(sha3("withdraw()"))); // if withdraw throws an exception, the raw call() will only return false and transaction will NOT be reverted

案例研究 - 众筹活动

在本节中,我们将为众筹活动使用案例实现并部署智能合约。

众筹的理念是从大众集资为项目或风险创业筹集资金的过程。投资者会收到代表他们投资创业公司股份的代币。项目设定了一个预定的目标和达到该目标的期限。一旦项目未达到目标,资金将被返还,这减少了投资者的风险。这种去中心化的筹资模式可以取代初创企业的资金需求,并且无需集中式的受信任平台。投资者只会在资金返还时支付 gas 费用。任何项目的贡献者都会得到一个代币,他们可以交易、出售或保留这些代币。在某一阶段,代币可以用来换取实际产品作为物理奖励。

定义结构和事件,如下所示:

pragma solidity ⁰.4.24;

contract CrowdFunding {

    Project public project;
    Contribution[] public contributions;
    //Campaign Status
    enum Status {
        Fundraising,
        Fail,
        Successful
    }
    event LogProjectInitialized (
        address owner,
        string name,
        string website,
        uint minimumToRaise, 
        uint duration
    );
    event ProjectSubmitted(address addr, string name, string url, bool initialized);
    event LogFundingReceived(address addr, uint amount, uint currentTotal);
    event LogProjectPaid(address projectAddr, uint amount, Status status);
    event Refund(address _to, uint amount);
    event LogErr (address addr, uint amount);
    //campaign contributors
    struct Contribution {
        address addr;
        uint amount;
    }
    //define project
    struct Project {
        address addr;
        string name;
        string website;
        uint totalRaised;
        uint minimumToRaise; 
        uint currentBalance;
        uint deadline;
        uint completeAt;
        Status status;
    }
    //initialized project
    constructor (address _owner, uint _minimumToRaise, uint _durationProjects, 
        string _name, string _website) public payable { 
        uint minimumToRaise = _minimumToRaise * 1 ether; //convert to wei
        uint deadlineProjects = now + _durationProjects* 1 seconds;
        project = Project(_owner, _name, _website, 0, minimumToRaise, 0, deadlineProjects, 0, Status.Fundraising);
        emit LogProjectInitialized(
            _owner,
            _name,
            _website,
            _minimumToRaise,
            _durationProjects);
    }

定义修饰词,如下代码所示:

   //check if project is at the required stage
    modifier atStage(Status _status) {
        require(project.status == _status,"Only matched status allowed." );
        _;
    }
    //check if msg.sender is project owner
    modifier onlyOwner() {
        require(project.addr == msg.sender,"Only Owner Allowed." );
        _;
    }
    //check if project pass the deadline
    modifier afterDeadline() {
        require(now >= project.deadline);
        _;
    }
    //Wait for 6 hour after campaign completed before allowing contract destruction
    modifier atEndOfCampain() {
        require(!((project.status == Status.Fail || project.status == Status.Successful) && project.completeAt + 6 hours < now));
        _;
    }

定义智能合约函数,如下所示:

   function () public payable {
       revert(); 
    }

    /* The default fallback function is called whenever anyone sends funds to a contract */
    function fund() public atStage(Status.Fundraising) payable {
        contributions.push(
            Contribution({
                addr: msg.sender,
                amount: msg.value
                })
            );
        project.totalRaised += msg.value;
        project.currentBalance = project.totalRaised;
        emit LogFundingReceived(msg.sender, msg.value, project.totalRaised);
    }
    //checks if the goal or time limit has been reached and ends the campaign
    function checkGoalReached() public onlyOwner afterDeadline {
        require(project.status != Status.Successful && project.status!=Status.Fail);
        if (project.totalRaised > project.minimumToRaise){
            project.addr.transfer(project.totalRaised);
            project.status = Status.Successful;
            emit LogProjectPaid(project.addr, project.totalRaised, project.status);
        } else {
            project.status = Status.Fail;
            for (uint i = 0; i < contributions.length; ++i) {
              uint amountToRefund = contributions[i].amount;
              contributions[i].amount = 0;
              if(!contributions[i].addr.send(contributions[i].amount)) {
                contributions[i].amount = amountToRefund;
                emit LogErr(contributions[i].addr, contributions[i].amount);
                revert();
              } else{
                project.totalRaised -= amountToRefund;
                project.currentBalance = project.totalRaised;
                emit Refund(contributions[i].addr, contributions[i].amount);
              }
            } 
        }
        project.completeAt = now;
    }
    function destroy() public onlyOwner atEndOfCampain {
        selfdestruct(msg.sender);
    }
}

让我们使用 Remix 来测试我们的活动。我们选择 JavaScript VM 选项。

  1. 通过点击以下输入的 Deploy 按钮来初始化活动。这将通过构造函数启动我们的活动。我们将第一个账户指定为项目所有者。最低筹资为 30 以太币,截止期限设置为五分钟用于测试目的。将以下输入代码放入 Deploy 按钮旁边的文本框中。以下是构造函数的输入参数:
      0xca35b7d915458ef540ade6068dfe2f44e8fa733c, 30, 100, "smartchart", 
      "smartchart.tech"

以下是该步骤 Remix 编辑器屏幕的截图:

Remix 编辑器屏幕

  1. 切换到第二个账户,在 Remix 的价值输入字段中输入20以太币,然后点击(回退)按钮。这将向 totalRaised 添加20以太币。要查看项目信息,点击项目按钮,你应该看到 totalRaised 现在是 20 以太币。在捐款输入字段中输入0以太币,我们可以看到第二个账户的捐款地址和金额为 20 以太币:

Remix 的价值输入字段

  1. 切换到第三个账户,在价值字段中输入15以太币,为项目增加资金。点击(回退)按钮,我们可以看到项目的总筹款量已经达到了 35 以太币。此时,项目已经实现了筹款目标:

为项目增加资金

  1. 切换回项目所有者,即第一个账户,并点击 checkGoalReached。我们可以看到交易已成功执行。在日志中,项目状态已更新为"成功"。LogProjectPaid 被触发。如果我们检查 Remix 账户 1、2、3,项目所有者账户现在包含大约 135 以太币。我们的活动智能合约在 Remix 中经过了成功的测试:

成功测试了智能合约中的活动

摘要

在本章中,我们学习了 Solidity 编程的基本特性。我们还概述了当前流行的智能合约开发工具。通过探索常见模式和安全最佳实践,我们学会了如何编写更好的代码,以避免合约漏洞。最后,我们编写了一个众筹合约,并使用 Remix 部署和测试了我们的示例。

在下一章中,我们将构建一个去中心化应用程序(DApp)用于众筹。

第十五章:构建以太坊区块链应用程序

在上一章中,我们回顾了智能合约的基本特性以及如何编写一个众筹智能合约示例。在我们将智能合约部署到区块链之后,我们需要编写 Web 应用程序来与智能合约交互。以太坊区块链通过调用智能合约函数和获取器提供了 web3 API。

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

  • 什么是去中心化应用DApp

  • web3js 快速概述

  • 设置以太坊开发环境

  • 开发和测试 DApp

去中心化应用概述

去中心化应用(或DApp)是一种利用智能合约运行的应用程序。智能合约部署在以太坊虚拟机EVM)上。它类似于客户端-服务器低层架构。一个 DApp 可以有一个前端(Web),通过 web3.js API 调用其后端(智能合约)。

以下结构是我们将为我们的众筹 DApp 构建的:

我们将为众筹 DApp 构建的结构

web3.js 快速概述

web3.js 是一个以太坊 JavaScript API,提供了一系列库来与本地或远程以太坊网络交互。web3js 与以太坊之间的连接是通过使用 HTTP 或 IPC 协议进行的。在下表中,我们快速审查了一些重要的 web3.js API 概念:

API 参考 描述 示例
web3-eth 该包提供了与以太坊区块链和智能合约交互的 API getBalancesendTransaction, coinbasegetBlockNumber, getAccounts
web3-shh 该包提供了与 whisper 协议进行广播交互的 API
web3.shh.post({
        symKeyID: identities[0],
        topic: '0xffaadd11',
        payload: '0xffffffdddddd1122'
    }).then(h => console.log(`Message with hash ${h} was successfuly sent`))

|

web3-bzz 该包提供了与以太坊疯蜂网络(Ethereum swarm)交互的 API,即去中心化文件存储平台 web3.bzz.currentProvider``web3.bzz.download(bzzHash [, localpath])
web3-utils 该包提供了一组针对以太坊 DApp 和其他 web3.js 包的实用函数 web3.utils.toWei(number [, unit])``web3.utils.isAddress(address)

提供者

提供者抽象了与以太坊区块链进行通信的连接。它将向区块链发出查询并发送交易。web3 提供了 JsonRpcProviderIpcProvider,允许您连接到本地或远程以太坊节点,包括 Mainnet、Ropsten 测试网、Kovan 测试网、Rinkeby 测试网和自定义的远程过程调用(RPC),如 Ganache。下面是展示如何使用 web3 API 连接以太坊节点的代码片段。

var Web3 = require('web3');
var web3 = new Web3('http://localhost:8545');
// or
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
// change provider
web3.setProvider('ws://localhost:8546');
// or
web3.setProvider(new Web3.providers.WebsocketProvider('ws://localhost:8546'));

DApp 开发工具

有一些流行的区块链 Web 开发工具被开发者用于创建 DApp 项目的基本结构。以下部分列出了其中的一些。

Truffle

Truffle 是一个以太坊 DApp 端到端开发工具,提供了一个开发环境,用于编写、编译和部署测试智能合约和 DApps。您可以为前端编写 HTML、CSS 和 JavaScript;Solidity 用于智能合约,并使用 web3.js API 与 UI 和智能合约交互。Truffle Boxes 提供了有用的样板文件,其中包含了有用的模块、Solidity 合约和库、前端代码以及许多其他有用的文件。Truffle Boxes 帮助开发人员快速启动他们的 DApp 项目。

Truffle 命令行使用以下格式:

  • truffle [command] [options]

这里是命令行工具中常用的选项:

命令 描述
compile 编译 Solidity 合约文件。
console 与部署的智能合约交互的命令行界面。
create 此命令可帮助创建新的合约、新的迁移文件和基本测试。
debug 在调试器会话中对特定交易进行实验。
deploy/migration 将合约部署到区块链网络。
develop 在本地开发环境中通过命令行与合约交互。
init 从以太坊包注册表中安装包。

Ganache

Ganache 是一个私有的以太坊区块链环境,允许您模拟以太坊区块链,以便您可以在自己的私有区块链中与智能合约交互。以下是 Ganache 提供的一些功能:

  • 显示区块链日志输出

  • 提供高级挖掘控制

  • 内置区块浏览器

  • 以太坊区块链环境

  • Ganache 有一个桌面应用程序和一个命令行工具

这是 Ganache 桌面版的外观:

命令行使用以下格式:

ganache-cli <options>

这些是命令行工具的常用选项:

选项 描述
-a 或 --accounts 启动时要生成的账户数量。
-e 或 --defaultBalanceEther 配置默认的测试账户以太币数量。默认值为 100
-b 或 --blockTime 指定以秒为单位的区块时间作为挖掘间隔。如果未指定此选项,当调用事务时,Ganache 将立即挖掘新块。
-h 或 --host 或 --hostname 指定要侦听的主机名。默认值为 127.0.0.1
-p 或 --port 指定端口号。默认值为 8545
-g 或 --gasPrice 以 Wei 指定气价(默认为 20000000000)。
-l 或 --gasLimit 区块气体限制(默认为 0x6691b7)。
--debug 为调试目的显示 VM 操作码。
-q 或 --quiet 不记录任何日志运行 ganache-cli

设置以太坊开发环境

按照以下说明获取以太坊开发工具,并启动以太坊私有本地区块链环境(主要用于在本地区块链上运行/部署您的智能合约)。

安装 Truffle

打开命令行并运行以下命令:

npm install -g truffle

安装 Ganache

打开命令行并安装 Ganache 的命���行接口,如下所示:

npm install -g ganache-cli

创建一个 Truffle 项目

要初始化一个新的 DApp 项目,我们可以运行 truffle init命令来初始化一个空的 Truffle 项目。这将创建 DApp 目录结构,包括应用程序、合约和测试以及 Truffle 配置。由于 Truffle Boxes 提供了许多可用的模板,在我们的 DApp 示例中,我们将使用宠物商店模板——一个 JavaScript UI 库的 JQuery 版本——来开发我们的众筹 DApp 示例。

创建一个名为Crowdfunding的文件夹,打开命令行提示符,导航到Crowdfunding文件夹,并运行以下命令:

truffle unbox pet-shop

项目结构如下:

我们在上一章中编写了众筹智能合约。让我们将CrowdFunding.sol文件复制到Crowdfunding下的 contracts 文件夹中。

启动 Ganache 环境

打开一个新的终端窗口并运行以下命令:

Ganache-cli

这将在端口8545上运行Ganache-cli,而且 Ganache 将为我们创建 10 个默认账户。每个账户默认拥有 100 以太币。你应该在控制台中看到类似这样的东西:

在我们的 Truffle 项目中,truffle.js7545定义为默认端口号。我们需要将端口号更新为8545以与 Ganache 的端口号匹配,如下所示:

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*" // Match any network id
    }
  }
};

部署智能合约

正如你可能已经注意到的,先前的命令创建了两个与迁移相关的文件,Migrations.sol1_initial_migration.jsMigrations.sol存储了对应于最后一个应用的“迁移”脚本的编号。当你添加一个新的合约并部署合约时,存储中的最后一个部署编号将增加。合约运行一次后,就不会再次运行。编号的约定是x_script_name.js,x 从 1 开始,即1_initial_migration.js。你的新合约通常会在以2_...开始的脚本中。

在我们的情况下,我们将添加一个新的迁移合约以部署CrowdFunding。让我们创建一个名为2_deploy_contracts.js的文件。

CrowdFunding.sol将构造函数定义如下:

constructor (address _owner, uint _minimumToRaise, uint _durationProjects,
        string _name, string _website)

要部署合约,可以调用 truffle deploy 函数,带有可选的构造函数参数,deployer.deploy(contract, args..., options)

我们将使用由 Ganache 提供的第一个账户作为所有者账户,如下所示:

var CrowdFunding = artifacts.require("./CrowdFunding.sol");
module.exports = (deployer, network, accounts) => {
  const ownerAddress = accounts[0];
  deployer.deploy(CrowdFunding, ownerAddress, 30, 60, "smartchart", "smartchart.tech");
}

让我们将智能合约部署到我们的网络。运行truffle命令,如下所示:

truffle migrate

以下截图显示了运行truffle migrate命令的结果:

这样可以在本地 Ganache 区块链环境中部署我们的众筹智能合约。

要启动本地节点服务器,请运行以下命令,这将在我们的浏览器中打开宠物商店页面:

npm run dev

编写众筹去中心化应用

我们刚刚在我们的本地 Ganache 区块链环境上部署了我们的智能合约。现在,我们将开始编写 UI 代码,通过 RPC 调用触发智能合约函数。本章的源代码可在 bit.ly/2X8xPBL 上找到。

选择一个 web3 提供程序

当我们加载网页时,我们需要连接到一个 web3 提供程序。如果您已经安装了像 MetaMask 这样的提供程序,您可以使用您的正确提供程序选项,如下所示:

App.web3Provider = web3.currentProvider;

在我们的众筹示例中,为了简单起见,我们将直接连接到我们的本地 Ganache 服务器,如下所示:

App.web3Provider = new Web3.providers.HttpProvider('http://localhost:8545');

加载账户信息

要加载账户,我们定义一个内容为空的下拉菜单,如下所示:

<div class="form-group">
                <label for="exampleFormControlSelect1">Accounts</label>
                <select class="form-control" id="accts">
                </select>
              </div>

当我们加载页面时,我们将使用 web3.eth.accounts 获取所有 10 个默认账户。请注意,第一个账户的以太均衡为 99.84;这是因为我们使用第一个账户作为所有者账户来部署合约,并燃烧了一些以太作为交易费用,如下代码所示:

    web3.eth.accounts.forEach( function(e){
        $('#accts').append($('<option>', {
            value:e,
            text : e + " (" +web3.fromWei(web3.eth.getBalance(e), "ether") + " ether)"
        }));
})

一旦账户加载完毕,将显示如下:

加载项目信息

在众筹中,我们定义了一个包含筹款信息的项目结构,如下所示:

struct Project {
        address addr;
        string name;
        string website;
        uint totalRaised;
        uint minimumToRaise;
        uint currentBalance;
        uint deadline;
        uint completeAt;
        Status status;  
    }

让我们在 HTML 中定义一些相关信息,例如:

<table class="table table-hover table-striped">
                  <tbody>
                    <tr>
                      <th scope="row">address</th>
                      <td><span class="text-info" id="address"></span
</td>
                    </tr>
                    <tr>
                      <th scope="row">name</th>
                      <td><span class="text-info" id="name"></span></td>
                    </tr>
                    <tr>
                        <th scope="row">website</th>
                        <td><span class="text-info" id="website"></span></td>
                    </tr>
                    <tr>
                        <th scope="row">totalRaised</th>
                        <td><span class="text-info" id="totalRaised"></span></td>
…
                  </tbody>
                </table>

CrowdFunding.deployed() 函数将创建一个代表由 CrowdFunding 管理的默认地址的 CrowdFunding 实例。这里的代码显示了我们如何显示项目信息:

    App.contracts.CrowdFunding.deployed().then(function(instance) {
      crowdFundingInstance = instance;
      return crowdFundingInstance.project();
    }).then(function(projectInfo) {
        $("#address").text(projectInfo[0].toString());
        $("#name").text(projectInfo[1]);
        $("#website").text(projectInfo[2]);
        $("#totalRaised").text(projectInfo[3].toString());
        ..
        if(projectInfo[6].toString().length>0) {
          var deadline = new
Date(Number(projectInfo[6].toString())*1000);
          deadlineDate = moment(deadline).format("YYYY-MM-DD h:mm:ss");
          $("#deadline").text(deadlineDate);
        }
        if(projectInfo[7].toString().length>0 && projectInfo[7].toString()!='0') {
          console.log(projectInfo[7].toString());
          var completeAt = new Date(Number(projectInfo[7].toString())*1000);
          completeAtDate = moment(completeAt).format("YYYY-MM-DD h:mm:ss");
          $("#completeAt").text(completeAtDate);
        }   
    }).catch(function(error) {
..
    });

结果将如下显示:

处理基金功能

要筹集资金,我们需要调用 fund 函数,在我们的众筹智能合约中定义。在我们的网页中,我们使用 HTML 范围输入滑块组件来贡献资金金额,如下所示:

<form id="fund-form" method="post" role="form" style="display: block;">
                                      <div class="form-group row">
                                          <div class="row">
                                              <div class="col-lg-12">
                                                  <input type="range" name="ageInputName" id="ageInputId" value="0" min="1" max="100" oninput="ageOutputId.value = ageInputId.value">
                                                  <div style="display: inline;"><output name="ageOutputName" id="ageOutputId">0</output> <span>ether</span></div>             
                                              </div>
                                            </div>
                                       </div>
                                      <div class="form-group">
                                        <div class="row">
                                          <div class="col-lg-12">
                                              <button type="button" id="fundBtn" class="btn btn-primary pull-left">Submit</button>
                                          </div>
                                        </div>
                                      </div>                              
                                  </form>

Crowdfunding fund 函数是一个可支付的回退函数;因此,我们需要从 UI 传递 msg.sendermsg.value 来调用它,如下所示。

   function fund() public atStage(Status.Fundraising) payable {
        contributions.push(
            Contribution({
                addr: msg.sender,
                amount: msg.value
                })
            );
……
}

您可以按如下方式定义发送地址和值参数:

  handleFund: function(event) {
    event.preventDefault();
    var fundVal =  $('#ageOutputId').val();
    var selectAcct = $('#accts').find(":selected").val();
    $("#displayMsg").html("");
    App.contracts.CrowdFunding.deployed().then(function(instance) {
      return instance.fund({ from: selectAcct, value:web3.toWei(fundVal, "ether"), gas:3500000});
    }).then(function(result) {
      App.loadProject();
    }).catch(function(err) {
      console.error(err);
      $("#displayMsg").html(err);
    });
  },

一旦我们收到结果返回,我们将调用 loadProject 函数刷新项目信息。我们可以看到当前余额基金增加了,如下截图所示:

checkGoalReached

一旦达到筹款目标,众筹所有者将通过运行 checkGoalReached 方法收集所有资金。

HTML 仅为一个简单的按钮,如下所示的代码:

<button type="button" id="checkGoal" class="btn btn-success">CheckGoal</button>

与 fund 函数类似,我们使用以下代码在 JavaScript 中调用智能合约:

instance.checkGoalReached({ from: selectAcct, gas:3500000});

这是详细的逻辑:

  handleCheckGoal: function(event) {
    event.preventDefault();
    $("#displayMsg").html("");
    var selectAcct = $('#accts').find(":selected").val();
    App.contracts.CrowdFunding.deployed().then(function(instance) {
      return instance.checkGoalReached({ from: selectAcct, gas:3500000});
    }).then(function(result) {
      App.loadProject();
    }).catch(function(err) {
      console.error(err);
      $("#displayMsg").html(err);
    });
  },

结果将如下显示:

如果您跟随整个示例并运行了这一步,恭喜!您现在可以编写并运行一个众筹 DApp 了。

概要

在本章中,我们学习了 DApp 的基础知识,现在我们了解了 web3.js API。通过在本地以太坊环境中运行 Ganache,我们可以使用 Truffle 开发工具创建一个众筹项目并编写一个 DApp 组件。最后,我们部署并启动了众筹 DApp。在下一章中,我们将开始探索最受欢迎的企业区块链——Hyperledger Fabric。

第十六章:使用 Hyperledger Fabric 探索企业区块链应用

在前一章中,我们讨论了以太坊区块链。以太坊是一个公共区块链;任何人都可以阅读区块链数据并进行合法的更改。任何人都可以向链中写入一个新区块。以太坊是完全自治的,不受任何人控制。智能合约用 Solidity 语言编写,作为一个几乎图灵完备的语言,可以在以太坊虚拟机 (EVM)上执行各种交易。开发人员可以使用这些智能合约构建和部署去中心化应用 (DApps)。以太是以太坊中的加密货币,作为执行以太坊中的每个操作的燃料,包括执行智能合约、DApps、交易等。然而,这并不是构建区块链的唯一方法。

可以创建需要在区块链节点中建立访问控制层以读取区块链上受限信息的区块链。这将限制网络中能够参与共识机制中交易的参与者数量。这种区块链称为许可区块链。

下表显示了公共和许可区块链之间的区别:

无需许可 许可

| 公共 | 每个人都可以阅读交易数据。每个人都可以验证区块中的交易。

  • 速度: 差

  • 一致性: 工作量证明

  • 区块链: 比特币、以太坊

  • 代币: 需要

| 每个人都可以阅读交易数据。只有预定义的用户可以验证交易。

  • 速度: 良好

  • 一致性: 工作量证明

  • 区块链: 卡斯帕后的以太坊

  • 代币: 需要

|

| 私有 | 只有预定义的用户可以读取交易数据。只有预定义的用户可以验证交易。

  • 速度: 良好

  • 一致性: 联邦拜占庭协议 (FBA)

  • 代币: 不需要

只有预定义的用户可以读取交易数据。只有有权的用户可以验证交易。

  • 速度: 良好

  • 一致性: 实用拜占庭容错算法 (PBFT)

  • 区块链: Hyperledger Fabric

  • 代币: 不需要

|

Hyperledger Fabric 是一种私有的许可区块链之一。在本章中,我们将讨论 Hyperledger Fabric 区块链。

Hyperledger Fabric 是一种开源企业区块链技术。该项目最初由 IBM 和数字资产贡献。Hyperledger Fabric 是 Linux 基金会托管的区块链项目之一。Hyperledger Fabric 中的智能合约称为链码,它定义了 Fabric 应用的业务逻辑。模块化的架构设计使 Fabric 能够支持高度的机密性、恢复能力、灵活性和可扩展性。Fabric 中的组件,如共识和成员服务,可以插拔式部署。

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

  • 发行声明

  • 设置 Hyperledger Fabric 环境

  • 编写链码

  • 配置 Hyperledger Fabric

发行索赔

在本节中,我们将探讨并实施一个发行索赔的用例。

没有人希望有保险索赔,但当事情出错和发生事故时,这可能会导致财务损失。这些损失将由您的保险单承担。传统的保险理赔流程几十年来都保持不变,因为流程中存在许多关键问题,包括虚假索赔、欺诈检测、缓慢和繁琐的索赔处理、人为错误、不佳的客户体验以及再保险中信息流的低效。

使用区块链,分类账中的交易记录是不可变的,只有当所有各方都同意时,状态数据才能更新。区块链中的记录可以实时共享。这使得保险公司可以迅速行动,因为大部分用于索赔验证的必要信息可以在短时间内处理。保险公司可以跟踪区块链中资产数据的使用情况。文件工作可以被消除,客户可以通过网络应用提交索赔。

让我们看一下保险索赔流程,如下截图所示。为了演示目的,我们简化了索赔流程,因为在实际应用中可能会更加复杂:

对于上述流程,步骤如下:

  1. 被保险人向经纪人报告索赔

  2. 经纪人提供请求的信息

  3. 经纪人向发行人提交索赔

  4. 发行人确认索赔

  5. 发行人处理并批准索赔

配置 Hyperledger Fabric 环境

到目前为止,我们已经了解了 Hyperledger Fabric 的关键概念。在本节中,我们将建立一个 Hyperledger Fabric 开发环境。在继续进行安装步骤之前,让我们看一下 fabric 安装的先决条件。

安装先决条件

安装所需的开发工具的先决条件如下:

Ubuntu Linux 14.04 / 16.04 LTS(均为 64 位),或 macOS 10.12 Docker Engine:版本 17.03 或更高
Docker-Compose:版本 1.8 或更高 Node:8.9 或更高(注意不支持 9 版本)
npm:v5.x git:2.9.x 或更高
Python:2.7.x

我们将在开发环境中使用 Ubuntu。我们可以使用以下命令下载先决条件:

curl -O https://hyperledger.github.io/composer/latest/prereqs-ubuntu.sh
chmod u+x prereqs-ubuntu.sh
./prereqs-ubuntu.sh

它可能在执行过程中提示您输入密码,因为它在执行过程中使用了sudo

安装 Hyperledger Fabric

创建并转到名为insurance-claim的项目文件夹,如下所示:

mkdir ~/insurance-claim && cd ~/insurance-claim

输入以下命令以安装 Hyperledger Fabric 特定于平台的二进制文件:

curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/release-1.3/scripts/bootstrap.sh | bash

执行此命令后,它会在bin文件夹中下载以下特定于平台的二进制文件,该文件夹位于fabric-samples文件夹下。您可以将fabric-samples/bin设置为PATH变量,如下所示:

export PATH=<path to download location>/bin:$PATH

我们还从本书的代码文件中提供 bootstrap-hyperledger.sh,您可以从 Packt 网站下载。一旦获得文件,您可以直接运行以下脚本,它将创建一个 bin 文件夹并将二进制文件下载到此文件夹中:

这些组件将成为我们的 Hyperledger Fabric 网络的一部分。

编写链码

Chaincode 类似于智能合约。它定义并执行由特定网络中的授权参与者调用的业务逻辑。链码是用 Go 或 Node.js 编写的。在我们的示例中,我们将使用 Go。

有许多 IDE 和工具支持 Golang。以下是一些与 Golang 配合得很好的流行 IDE。

开发工具

有各种工具支持 Go 开发。一些流行的 IDE 在以下部分列出。

LiteIDE

LiteIDE 是专为 Golang 设计的开源 Go IDE。有许多适用于 Go 开发人员的有用功能,包括可配置的代码编辑器、自定义构建命令、许多构建选项和 Golang 支持。

JetBrains Gogland

Gogland 拥有强大的内置自动完成引擎、错误检测、代码重构工具等等。

Visual Studio Code

您可以在 Visual Studio Code 中安装 Go 扩展。它提供代码提示和调试代码的能力。

在本章中,我们将使用 LiteIDE 来开发我们的链码。请按照官方 LiteIDE 安装指南设置您的本地 IDE 环境,该指南可从以下链接获取:

github.com/visualfc/liteide/blob/master/liteidex/deploy/welcome/en/install.md.

Chaincode 关键概念和 API

Fabric 链码中有三个重要的函数:InitInvokeQuery。每个链码程序都必须实现链码接口,如下所示:

type Chaincode interface {
    Init(stub ChaincodeStubInterface) pb.Response
    Invoke(stub ChaincodeStubInterface) pb.Response
}

Init()在应用程序初始化其内部数据以供其他链码函数使用时被调用。当链码接收到实例化或升级事务时,它将被触发。

当应用程序客户端提出更新或查询事务时,将调用Invoke()函数。

当链码查询链码状态时,将调用Query()。Hyperledger Fabric 使用 LevelDB(键值存储)作为存储 world;state 数据的默认数据库。您可以使用键来获取当前账本状态数据。查询函数通过传入键值读取链码状态的值。

shim 包提供了用于链码访问其状态变量、事务上下文和调用其他链码的 API。

ChaincodeStubInterface 是其中一个重要的接口。它提供了各种函数,让您可以查询、更新和删除账本中的资产。这些函数如下所示:

GetState(key string) ([]byte, error) GetState 函数从账本中返回指定键的值。
PutState(key string, value []byte) error PutState将指定的键和值放入交易的写集作为数据写提案
DelState(key string) error DelState记录了交易提案的写集中将要删除的指定键

定义发行理赔

让我们写一段链码。打开 LiteIDE 并创建一个名为claimcontract.go的新文件,如下所示:

在保险理赔使用案例分析中,我们分析了发行理赔流程中的参与者。我们需要定义一个链码的参与者有三个:被保险人、经纪人和保险人,如下例所示:

type Insuree struct {
         Id           string `json:"id"`
         FirstName    string `json:"firstName"`
         LastName     string `json:"lastName"`
         SSN          string `json:"ssn"`
         PolicyNumber string `json:"policyNumber"`
}

Insuree中,我们定义了IdfirstnameLastNameSSNpolicyNumber

在 Go 语言中,字段名的第一个字母可以是大写或小写。当我们需要一个导出字段对任何代码使用时,它需要是一个大写字母。你可以使用 JSON 包中的编码将数据解析为结构体,定义 JSON 中的字段名为firstName,如下所示:

type Member struct {
  Name string `json:"member_name"`
}

经纪人和保险人的数据模型类似,只是类型不同。我们将其定义如下:

type Company struct {
         Id   string `json:"id"`
         Type string `json:"type"`
         Name string `json:"name"`
}

在发行理赔流程中,Insuree初始化理赔请求。理赔文件将在区块链中跟踪流程的每一步。它记录了所有必要的信息,包括状态、用户理赔描述、insueeIdbrokerIdinsurerId、每个步骤的处理时间以及授权方输入的评论,如下例所示:

type Claim struct {
         Id        string `json:"id"`        //the fieldtags are needed to keep case from bouncing around
         Desc      string `json:"desc"`      //claim description
         Status    string `json:"status"`    //status of claim
         InsureeId string `json:"insureeId"` //InsureeId
         BrokerId  string `json:"brokerId"`  //BrokerId
         InsurerId string `json:"insurerId"` //InsurerId
         Comment   string `json:"comment"`   //comment
         ProcessAt string `json:"processAt"` //processAt
}

初始化链码

接下来,我们将实现Init函数。Init()允许链码初始化被保险人数据以开始理赔请求。在我们的情况下,我们将设置并注册被保险人的个人信息,如下所示:

func (c *ClaimContract) Init(stub shim.ChaincodeStubInterface) pb.Response {
         args := stub.GetStringArgs()
         if len(args) != 5 {
                 return shim.Error("Incorrect arguments. Expecting a key and a value")
         }
         insureeId := args[0]
         firstName := args[1]
         lastName := args[2]
         ssn := args[3]
         policyNumber := args[4]
         insureeData := Insuree{
                 Id:           insureeId,
                 FirstName:    firstName,
                 LastName:     lastName,
                 SSN:          ssn,
                 PolicyNumber: policyNumber}
         insureeBytes, _ := json.Marshal(insureeData)
         err := stub.PutState(insureeId, insureeBytes)
         if err != nil {
                 return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
         }
         return shim.Success(nil)
}

ChaincodeStubInterface.GetStringArg获取输入参数。它期望参数的长度应为 5。拥有所有必需的保险人数据后,我们构建保险人 JSON 数据,并将其编码为 JSON 字节字符串–json.Marshal(insureeData)。然后,我们将键和值存储在分类账上。如果一切顺利,它将返回一个成功的peer.Response对象给 Fabric 的client.c

调用链码

要触发调用函数,可以调用链码应用函数的名称,并将shim.ChaincodeStubInterface作为签名传递。在保险理赔案例中,我们定义了几个函数来支持我们的用例,例如:

AddCompany, ReportLost, RequestedInfo, SubmitClaim, ConfirmClaimSubmission, ApproveClaim.

我们还定义了一个查询来跟踪当前理赔请求和getHistory来获取所有历史理赔交易记录,如下所示:

func (c *ClaimContract) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
         function, args := stub.GetFunctionAndParameters()
         if function == "AddCompany" {
                 return c.AddCompany(stub, args)
         } else if function == "ReportLost" {
                 return c.ReportLost(stub, args)
         } else if function == "RequestedInfo" {
                 return c.RequestedInfo(stub, args)
         } else if function == "SubmitClaim" {
                 return c.SubmitClaim(stub, args)
         } else if function == "ConfirmClaimSubmission" {
                 return c.ConfirmClaimSubmission(stub, args)
         } else if function == "ApproveClaim" {
                 return c.ApproveClaim(stub, args)
         } else if function == "query" {
                 return c.query(stub, args)
         } else if function == "getHistory" {
                 return c.getHistory(stub, args)
         }

         return shim.Error("Invalid function name")
}

AddCompany

AddCompany类似于我们在初始化步骤添加被保险人的方式。链码可以通过此函数注册经纪人和保险人。公司类型可以是经纪人保险人,如下所示:

func (c *ClaimContract) AddCompany(stub shim.ChaincodeStubInterface, args []string) pb.Response {
         id := args[0]
         name := args[1]
         companyType := args[2]
         companyData := Company{
                 Id:   id,
                 Type: companyType,
                 Name: name}
         companyBytes, _ := json.Marshal(companyData)
         stub.PutState(id, companyBytes)
         return shim.Success(companyBytes)
}

报告丢失

在此步骤中,投保人向经纪人报告丢失物品,并提供所有索赔信息。此函数还在processAt字段记录当前系统处理时间。currentts.Format(2006-01-02 15:04:05)是一个 Go 自定义格式;它将当前时间转换为 YYYY-MM-dd hh:mm:ss 格式,如以下示例所示:

func (c *ClaimContract) ReportLost(stub shim.ChaincodeStubInterface, args []string) pb.Response {
         claimId := args[0]
         desc := args[1]
         insureeId := args[2]
         brokerId := args[3]
         currentts := time.Now()
         processAt := currentts.Format("2006-01-02 15:04:05")
         //initialized claim
         claimData := Claim{
                 Id:        claimId,
                 Desc:      desc,
                 Status:    "ReportLost",
                 InsureeId: insureeId,
                 BrokerId:  brokerId,
                 InsurerId: "",
                 Comment:   "",
                 ProcessAt: processAt}
         claimBytes, _ := json.Marshal(claimData)
         stub.PutState(claimId, claimBytes)
         return shim.Success(claimBytes)
}

RequestedInfo

投保人报告损失后,下一步是经纪人返回RequestedInfo,如下所示:

func (c *ClaimContract) RequestedInfo(stub shim.ChaincodeStubInterface, args []string) pb.Response {
         return c.UpdateClaim(stub, args, "RequestedInfo")
}
func (c *ClaimContract) UpdateClaim(stub shim.ChaincodeStubInterface, args []string, currentStatus string) pb.Response {
         claimId := args[0]
         comment := args[1]
         claimBytes, err := stub.GetState(claimId)
         claim := Claim{}
         err = json.Unmarshal(claimBytes, &claim)
         if err != nil {
                 return shim.Error(err.Error())
         }
         if currentStatus == "RequestedInfo" && claim.Status != "ReportLost" {
                 claim.Status = "Error"
                 fmt.Printf("Claim is not initialized yet")
                 return shim.Error(err.Error())
         } else if currentStatus == "SubmitClaim" && claim.Status != "RequestedInfo" {
                 claim.Status = "Error"
                 fmt.Printf("Claim must be in RequestedInfo status")
                 return shim.Error(err.Error())
         } else if currentStatus == "ConfirmClaimSubmission" && claim.Status != "SubmitClaim" {
                 claim.Status = "Error"
                 fmt.Printf("Claim must be in Submit Claim status")
                 return shim.Error(err.Error())
         } else if currentStatus == "ApproveClaim" && claim.Status != "ConfirmClaimSubmission" {
                 claim.Status = "Error"
                 fmt.Printf("Claim must be in Confirm Claim Submission status")
                 return shim.Error(err.Error())
         }
         claim.Comment = comment
         if currentStatus == "RequestedInfo" {
                 insurerId := args[2]
                 claim.InsurerId = insurerId
         }
         currentts := time.Now()
         claim.ProcessAt = currentts.Format("2006-01-02 15:04:05")
         claim.Status = currentStatus
         claimBytes0, _ := json.Marshal(claim)
         err = stub.PutState(claimId, claimBytes0)
         if err != nil {
                 return shim.Error(err.Error())
         }
         return shim.Success(claimBytes0)
}

由于剩余的流程函数非常相似,我们将UpdateClaim定义为一个通用函数,与剩余步骤共享。

UpdateClaim函数首先从输入参数中获取claimId和当前参与者评论。然后,它查询并从区块链中获取索赔以解码索赔数据,并将其转换为 JSON 字符串—json.Unmarshal(claimBytes, &claim)

在更新索赔内容之前,它将验证输入的索赔状态并确保其处于预期步骤上。如果一切顺利,我们将更新索赔状态、参与者评论和处理时间。

最后,我们使用claimId作为键在账本上更新索赔数据。

提交索赔,确认提交索赔,批准索赔

提交、确认和批准索赔与RequestedInfo非常相似,并且这些步骤由UpdateClaim函数调用。只有评论、状态和处理时间值不同。

查询

查询是您从分类帐中读取数据的方式。查询函数用于查询链码的状态。由于我们将索赔数据存储在以claimId为键的分类帐中,为了读取当前索赔,我们调用GetState,传递claimId作为键,如下所示:

func (c *ClaimContract) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
         var ENIITY string
         var err error
         if len(args) != 1 {
                 return shim.Error("Incorrect number of arguments. Expected ENIITY Name")
         }
         ENIITY = args[0]
         Avalbytes, err := stub.GetState(ENIITY)         if err != nil {
                 jsonResp := "{\"Error\":\"Failed to get state for " + ENIITY + "\"}"
                 return shim.Error(jsonResp)
         }
         if Avalbytes == nil {
                 jsonResp := "{\"Error\":\"Nil order for " + ENIITY + "\"}"
                 return shim.Error(jsonResp)
         }
         return shim.Success(Avalbytes)
}

getHistory

正如其名称所示,gethistory函数读取一个键的所有历史值记录的索赔,以及TxId和索赔值。

首先我们定义了AuditHistory结构,其中包含TxId和值。GetHistoryForKey通过resultsIterator返回结果列表,其中包含所有历史交易记录。我们遍历这些记录并将它们添加到一个AuditHistory数组中。后来,我们将其转换为 JSON 字节并作为响应发送回来,如下所示:

func (c *ClaimContract) getHistory(stub shim.ChaincodeStubInterface, args []string) pb.Response {
         type AuditHistory struct {
                 TxId  string `json:"txId"`
                 Value Claim  `json:"value"`
         }
         var history []AuditHistory
         var claim Claim
         if len(args) != 1 {
                 return shim.Error("Incorrect number of arguments. Expecting 1")
         }
         claimId := args[0]
         fmt.Printf("- start getHistoryForClaim: %s\n", claimId)

         // Get History
         resultsIterator, err := stub.GetHistoryForKey(claimId)
         if err != nil {
                 return shim.Error(err.Error())
         }
         defer resultsIterator.Close()

         for resultsIterator.HasNext() {
                 historyData, err := resultsIterator.Next()
                 if err != nil {
                          return shim.Error(err.Error())
                 }
                 var tx AuditHistory
                 tx.TxId = historyData.TxId
                 json.Unmarshal(historyData.Value, &claim)
                 tx.Value = claim              //copy claim over
                 history = append(history, tx) //add this tx to the list
         }
         fmt.Printf("- getHistoryForClaim returning:\n%s", history)

         //change to array of bytes
         historyAsBytes, _ := json.Marshal(history) //convert to array of bytes
         return shim.Success(historyAsBytes)
}

这涵盖了我们的发行索赔链码。我们将在下一节学习有关 Hyperledger Fabric 配置的信息。

配置 Hyperledger Fabric

保险索赔网络中有三个实体—投保人、经纪人和保险公司。所有这些参与者都将在 Fabric 中注册为对等节点。以下表格描述了三个对等角色和 MSP 信息:

用户 ID 角色 组织 MSP ID
user_001 投保人 Org1MSP
broker_001 经纪人 Org2MSP
insurer_001 保险公司 Org3MSP

我们有一个保险人加入了具有 MSP ID org1 的组织,一个经纪人加入了具有 MSP ID org2 的组织,以及一个保险人加入了具有 MSP ID org3 的组织。为了引导 fabric 网络,我们需要首先为我们需要运行的三个组件生成加密材料。

生成证书

我们需要定义 crypto-config.yaml 并使用 cryptogen 工具为每个节点生成证书。Cryptogen 可在工具镜像中获得。crypto-config.yaml 包含以下信息:

  • OrdererOrgs:管理排序节点的组织定义

  • PeerOrgs:管理对等节点的组织定义

OrdererOrgs 包含关于集群中排序节点的以下信息:

  • 名称:订单者的名称

  • :订单者的域 URL;在我们的案例中,它是 ic.com

  • 主机名:订单者的主机名

这里是一个例子:

OrdererOrgs:
  - Name: Orderer
    Domain: ic.com
    Specs:
      - Hostname: orderer

PeerOrgs 包含关于集群中对等节点的以下信息:

  • 名称:组织的名称;我们有三个不同的组织:Org1Org2Org3

  • 模板计数:组织的节点数

  • 用户计数:组织的用户数

这里是一个例子:

PeerOrgs:
  # ---------------------------------------------------------------------------
  # Org1
  # ---------------------------------------------------------------------------
  - Name: Org1
    Domain: org1.ic.com
    Template:
      Count: 2
    Users:
      Count: 1
  # ---------------------------------------------------------------------------
  # Org2
  # ---------------------------------------------------------------------------
  - Name: Org2
    Domain: org2.ic.com
    Template:
      Count: 2
    Users:
      Count: 1
  # ---------------------------------------------------------------------------
  # Org3
  # ---------------------------------------------------------------------------
  - Name: Org3
    Domain: org3.ic.com
    Template:
      Count: 2
    Users:
      Count: 1

以下是用于生成加密材料的命令:

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

运行 cryptogen 工具后,您应该在控制台上看到以下输出:

生成订单者创世区块

生成证书后,该过程的下一步是生成订单者创世区块。configtxgen 命令允许用户创建和检查通道配置。configtxgen 工具的输出主要由 configtx.yaml 的内容控制,如下所示:

Profiles:
    ICOrgsOrdererGenesis:
        Orderer:
            <<: *OrdererDefaults
            Organizations:
                - *OrdererOrg
        Consortiums:
            InsuranceClaimConsortium:
                Organizations:
                    - *Org1
                    - *Org2
                    - *Org3
    ICOrgsChannel:
        Consortium: InsuranceClaimConsortium
        Application:
            <<: *ApplicationDefaults
            Organizations:
                - *Org1
                - *Org2
                - *Org3
Organizations:
    - &OrdererOrg
        Name: OrdererOrg
        ID: OrdererMSP
        MSPDir: crypto-config/ordererOrganizations/ic.com/msp
    - &Org1
        Name: Org1MSP
        ID: Org1MSP
        MSPDir: crypto-config/peerOrganizations/org1.ic.com/msp
        AnchorPeers:
            - Host: peer0.org1.ic.com
              Port: 7051
    - &Org2
        Name: Org2MSP
        ID: Org2MSP
        MSPDir: crypto-config/peerOrganizations/org2.ic.com/msp
        AnchorPeers:
            - Host: peer0.org2.ic.com
              Port: 7051         
    - &Org3
        Name: Org3MSP
        ID: Org3MSP
        MSPDir: crypto-config/peerOrganizations/org3.ic.com/msp

        AnchorPeers:
            - Host: peer0.org3.ic.com
              Port: 7051              
Orderer: &OrdererDefaults
    OrdererType: solo
    Addresses:
        - orderer.ic.com:7050
    BatchTimeout: 2s
    BatchSize:
        MaxMessageCount: 10
        AbsoluteMaxBytes: 20 MB
        PreferredMaxBytes: 512 KB
    Kafka:
        Brokers:
            - 127.0.0.1:9092
    Organizations:
Application: &ApplicationDefaults

    Organizations:

我们在 configtx 文件的 Organizations 部分定义了三个组织;我们指定了每个组织的名称、IDMSPDirAnchorPeersMSPDir 描述了 cryptogen 生成的输出 MSP 目录。AnchorPeers 指向节点的主机和端口。它更新事务以便在不同组织的节点之间启用通信,并找到通道的所有活动参与者,如下所示:

configtxgen -profile ICOrgsOrdererGenesis -outputBlock ./channel-artifacts/genesis.block

类似以下的输出将显示在控制台上:

生成通道配置事务

configtxgen 通过执行通道配置事务将通道创建事务写入 channel.tx,如下所示:

configtxgen -profile ICOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID icchannel

类似以下的输出将显示在控制台上:

执行通道配置事务的输出

Hyperledger Fabric Docker 组合器配置文件概述

Hyperledger Fabric 利用 Docker compose 来定义 fabric 应用服务。docker-compose-cli.yaml 服务部分是定义所有对等服务和相关容器的地方。Hyperledger Fabric 的 first-network 提供了一个 .yaml 模板,帮助您快速开始从头创建 yaml 文件:

github.com/hyperledger/fabric-samples/tree/release-1.2/first-network

docker-compose-cli.yaml 中,我们定义了以下信息:

  • networks:定义区块链网络名称。在我们的案例中,它是 icn

  • services:定义所有对等服务和相关 Docker 容器

  • cli:定义了用于替代 SDK 客户端的 Cli 容器,并设置了 Docker compose 命令行行为的环境变量

这是网络和服务部分的示例配置:

networks:
  icn:
services:
  orderer.ic.com:
    extends:
      file:   base/docker-compose-base.yaml
      service: orderer.ic.com
    container_name: orderer.ic.com
    networks:
      - icn
  peer0.org1.ic.com:
    container_name: peer0.org1.ic.com
    extends:
      file:  base/docker-compose-base.yaml
      service: peer0.org1.ic.com
    networks:
      - icn

如您所见,有一个文件扩展目录:base/docker-compose-base.yaml。Docker compose 支持使用 extends 字段为单个服务共享公共配置。我们稍后会详细讨论 docker-compose-base.yaml

这是 cli 部分的配置示例:

cli:
    container_name: cli
    image: hyperledger/fabric-tools
    tty: true
    environment:
      - GOPATH=/opt/gopath
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_ID=cli
      - CORE_PEER_ADDRESS=peer0.org1.ic.com:7051
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.ic.com/peers/peer0.org1.ic.com/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.ic.com/peers/peer0.org1.ic.com/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.ic.com/peers/peer0.org1.ic.com/tls/ca.crt
      - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.ic.com/users/[email protected]/msp
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: /bin/bash -c './scripts/script.sh ${CHANNEL_NAME} ${DELAY}; sleep $TIMEOUT'
    #for mapping the directories that are being used in the environment configurations
    volumes:
        - /var/run/:/host/var/run/
        - ./chaincode/:/opt/gopath/src/github.com/chaincode
        - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
        - ./scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/
        - ./channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts
    depends_on:
      - orderer.ic.com
      - peer0.org1.ic.com
      - peer0.org2.ic.com
      - peer0.org3.ic.com
    networks:
      - icn

docker-compose 工具使用 docker-compose-cli.yaml 文件来初始化 fabric 运行时环境。以下是在使用 docker-compose-cli.yaml 文件时您将使用的一些最常见的命令:

TTY TTY 基本上意味着 控制台,我们将其设置为 true。
Image 指向 fabric-tools 图像目录。
Environment 指定环境变量,例如 GOPATH,由 cryptogen 工具生成的与 TLS 相关的文件位置。
working_dir 设置了对等体的工作目录。
command 指定容器启动时发出的命令。
volumes 映射了在环境配置中使用的目录。
depends_on 按依赖顺序启动服务。

然后生成四个 fabric-peer 事务节点容器,一个 fabric-order 订单节点容器和一个 fabric-tools cli 容器。

Fabric 项目目录结构

在我们的 Fabric 示例 first-network 中,项目结构类似于以下内容:

正如我们之前讨论的,docker-compose-cli.yaml 服务是从 base/docker-compose-base.yaml 继承的。有两个文件基目录:peer-base.yamldocker-compose-base.yaml

Docker-compose-base.yaml

此文件包含基本配置,包括每个对等体和订单容器的环境和端口号。这定义了保险索赔网络的整体拓扑,如下:

services:
  orderer.ic.com:
    container_name: orderer.ic.com
    image: hyperledger/fabric-orderer
    environment:
      - ORDERER_GENERAL_LOGLEVEL=debug
      - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
      - ORDERER_GENERAL_GENESISMETHOD=file
      - ORDERER_GENERAL_GENESISFILE=/var/hyperledger/orderer/orderer.genesis.block
      - ORDERER_GENERAL_LOCALMSPID=OrdererMSP
      - ORDERER_GENERAL_LOCALMSPDIR=/var/hyperledger/orderer/msp
      # enabled TLS
      - ORDERER_GENERAL_TLS_ENABLED=true
      - ORDERER_GENERAL_TLS_PRIVATEKEY=/var/hyperledger/orderer/tls/server.key
      - ORDERER_GENERAL_TLS_CERTIFICATE=/var/hyperledger/orderer/tls/server.crt
      - ORDERER_GENERAL_TLS_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt]
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric
    command: orderer
    volumes:
    - ../channel-artifacts/genesis.block:/var/hyperledger/orderer/orderer.genesis.block
    - ../crypto-config/ordererOrganizations/ic.com/orderers/orderer.ic.com/msp:/var/hyperledger/orderer/msp
    - ../crypto-config/ordererOrganizations/ic.com/orderers/orderer.ic.com/tls/:/var/hyperledger/orderer/tls
    ports:
      - 7050:7050

  peer0.org1.ic.com:
    container_name: peer0.org1.ic.com
    extends:
      file: peer-base.yaml
      service: peer-base
    environment:
      - CORE_PEER_ID=peer0.org1.ic.com
      - CORE_PEER_ADDRESS=peer0.org1.ic.com:7051
      - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.ic.com:7051
      - CORE_PEER_LOCALMSPID=Org1MSP
    volumes:
        - /var/run/:/host/var/run/
        - ../crypto-config/peerOrganizations/org1.ic.com/peers/peer0.org1.ic.com/msp:/etc/hyperledger/fabric/msp
        - ../crypto-config/peerOrganizations/org1.ic.com/peers/peer0.org1.ic.com/tls:/etc/hyperledger/fabric/tls
    ports:
      - 7051:7051
      - 7053:7053
…..

Peer-base.yaml

这个文件为保险索赔 docker-compose-base.yaml 定义了对等网络配置,如下:

services:
  peer-base:
    image: hyperledger/fabric-peer
    environment:
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=${COMPOSE_PROJECT_NAME}_icn
      - CORE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_GOSSIP_USELEADERELECTION=true
      - CORE_PEER_GOSSIP_ORGLEADER=false
      - CORE_PEER_PROFILE_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: peer node start

在对等体中的命令让对等体安装系统链码和其他配置。

我们了解了关键的 Hyperledger Fabric 配置文件的概述,所以让我们使用以下代码启动我们的保险索赔网络:

      - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: peer node start

启动 Hyperledger Fabric 网络

现在,是时候启动我们的 Hyperledger Fabric 网络了。我们将使用 Docker 命令来初始化新的 Docker 组合:

docker-compose -f docker-compose-cli.yaml up

Docker 容器将触发在 docker-compose-cli.yaml 中定义的命令,如下所示:

command: /bin/bash -c './scripts/script.sh

script.sh 是一个包含一系列用于部署和测试命令的脚本。我们还在 utils.sh 中定义了一些业务特定的 shell 脚本函数。

创建一个通道

首先,我们需要创建一个通道以构建创世块。运行以下命令:

peer channel create -o orderer.ic.com:7050 -c icchannel -f ./channel-artifacts/channel.tx

这个命令从 channel.tx 中读取一个创世块,然后用于加入通道并创建 icchannel 通道。这是控制台上的结果:

控制台的输出,加入并创建通道

加入通道

在订购服务创建通道之后,我们可以将对等节点添加到通道中,如下所示:

peer channel join -b icchannel.block

这是控制台上的结果:

将对等节点添加到通道中

我们可以看到 peer0.org1peer0.org2peer0.org3 加入了通道。

更新锚点

在我们开始与我们的发行索赔网络进行交互之前,我们需要完成的最后一个操作是更新锚定对等点。锚定对等点接收并广播事务更新给组织中的其他对等点。锚定对等点在网络中是可搜索的。因此,任何注册为锚定对等点的对等点都可以被订购对等点或任何其他对等点发现,例如:

peer channel update -f ./channel-artifacts/Org1MSPanchors.tx -c icchannel -o orderer.ic.com:7050 --tls true --cafile $ORDERER_CA

这是此步骤的控制台输出:

被订购对等节点或任何其他对等节点发现

安装链码

在之前的步骤之后,我们几乎可以使用我们的发行索赔区块链应用程序了。但是首先,我们需要在我们的网络上安装 claimcontract.go 链码,如下所示:

peer chaincode install -n iccc -v 1.0 -l golang -p github.com/chaincode/claimcontract

我们将看到前述命令的输出:

安装链码到我们的网络

实例化链码

在安装链码之后,我们需要实例化它。正如我们之前讨论的,我们将在 init() 链码中引入被保人。因此,我们需要传递必需的参数来创建一个被保人参与者,如下所示:

peer chaincode instantiate -o orderer.ic.com:7050 -C icchannel -n iccc -l golang -v 1.0 -c '{"Args":[ "user_001","John","Smith", "9999","4394497111/1"]}' -P "OR    ('Org1MSP.member'

这是此步骤的输出:

创建一个被保人参与者

我们查询被保人以验证记录是否已在区块链中创建,如下所示:

peer chaincode query -C $CHANNEL_NAME -n iccc -c '{"Args":["query","user_001"]}'

从这个输出中我们可以看到,被保人(user_001)已经被添加到我们的区块链中:

被保人添加到我们的区块链中

调用添加经纪人

让我们将一个经纪人加入到我们的保险索赔区块链中,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["AddCompany","broker_001","BROKER","BNC Brokerage"]}'

这是结果:

将经纪人引入我们的保险索赔区块链

调用添加保险商

将最后一方保险商添加到保险索赔区块链中,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["AddCompany","insurer_001","INSURER","Western Insurance"]}'

显示的输出如下:

将最后一方保险商添加到保险索赔区块链中

调用 ReportLost

所有参与者都已加入网络,现在是开始保险索赔流程的时候了。被保险人向经纪人报告索赔,以下是调用'ReportLost'链代码的命令。

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["ReportLost","claim_001", "I was in Destiny shopping center and lost my IPhone 8", "user_001", "broker_001"]}

将显示以下输出:

被保险人向经纪人报告索赔

调用 RequestedInfo

经纪人提供请求的信息,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["RequestedInfo","claim_001", "Broker processsed user John Smith report and sent Requested Info to user.", "insurer_001"]}'

将显示以下输出:

提供所请求的信息

调用 SubmitClaim

经纪人向发行人提交索赔,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["SubmitClaim","claim_001", "Broker submitted a claim"]}'

将显示以下输出:

向发行人提交索赔

调用 ConfirmClaimSubmission

发行人确认索赔,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["ConfirmClaimSubmission","claim_001", "Insurer received and confirmed a claim"]}'

将显示以下输出:

确认索赔

调用 ApproveClaim

发行人处理并批准索赔,如下所示:

peer chaincode invoke -o orderer.ic.com:7050 -C icchannel -n iccc -c '{"Args":["ApproveClaim","claim_001", "Insurer processed and approved the claim."]}'

将显示以下输出:

处理并批准索赔

查询索赔历史

在发行人批准索赔后,整个流程完成,我们可以使用 Fabric API 查询索赔的整个生命周期,如下所示:

peer chaincode query -C icchannel -n iccc -c '{"Args":["getHistory","claim_001"]}'

从此查询获得的输出中,我们可以看到索赔请求的整个 Fabric 交易历史。

测试执行结束。

端到端测试执行

我们已经完成了保险索赔流程的每一步。为了简化整个端到端应用程序流程,您可以导航到insurance-claim文件夹,然后运行以下命令:

cd ~/insurance-claim
#change path if insurance-claim directory is different
export PATH=/home/ubuntu/insurance-claim/bin:$PATH 
./icn.sh -m up

输出结果如下:

简化整个端到端应用程序流程

最终输出如下:

保险索赔端到端测试完成

摘要

在本章中,我们学习了 Hyperledger Fabric 的基础知识。在设置开发环境之后,我们为保险索赔用例编写了链代码。然后,我们学习了面料组合器配置。最后,我们对我们的保险索赔应用程序进行了端到端的面料测试执行。我们可以看到,使用 Hyperledger Fabric 实现保险索赔应用程序相当复杂。在下一章中,我们将学习如何使用 Hyperledger Composer 快速编写保险索赔应用程序。

第十七章:使用 Hyperledger Composer 实现业务网络

Hyperledger Composer 是一个高级工具集和框架,旨在快速构建和运行基于 Hyperledger Fabric 区块链的应用程序。

在前一章中我们了解了 Hyperledger Fabric,所以你已经知道开发基于 Fabric 的应用程序相当复杂,因为它需要处理许多网络级别的配置。

在本章中,我们将讨论以下主题:

  • Hyperledger Composer — 一个快速概述

  • 设置 Hyperledger Composer 环境

  • 分析业务场景

  • 业务网络存档

  • 实现业务交易功能

Hyperledger Composer — 一个快速概述

Hyperledger Composer 是一组基于 JavaScript 的高级工具集和框架,可以简化并快速构建和运行基于 Hyperledger Fabric 区块链的应用程序。业务所有者和开发人员可以通过 composer 工具快速创建智能合约和应用程序。Composer 工具生成一个 RESTful 端点,用于与 Fabric 通道交互。与使用 Golang 编写链码不同,Composer 使用模型语言为区块链网络生成业务网络存档(.BNA)文件。

这是一个 Hyperledger Composer 解决方案架构的示例:

Hyperledger Composer 包含以下部分。

Yeoman 生成器

Yeoman 中的 npm 模块 generator-hyperledger-composer 用于创建 Hyperledger Composer 的模板。它支持并生成三种不同类型的模板:

  • CLI 应用程序

  • Angular 2 应用程序

  • 骨架业务网络

你可以使用 Yeoman 生成的 angular 骨架连接到 Hyperledger Composer REST 服务器。

Composer REST 服务器

Composer 的 REST 服务器利用一个独立的 Node.js 进程,并从部署的 composer 业务网络公开一组 RESTful API 端点。这些生成的 API 可以与 fabric 链码进行交互。侧面的代码然后可以触发 创建读取更新删除CRUD)资产、参与者和交易。

LoopBack 连接器

LoopBack 连接器利用 Node.js 的 LoopBack 框架来为业务网络中定义的资产、参与者和交易暴露 GET/POST/PUT/DELETE 操作。

JavaScript SDK

JavaScript SDK API 用于与部署的业务网络交互。它由客户端和管理 API 组成。

客户端 API 提供了从客户端应用程序查询、创建、更新和删除资源(资产和参与者),以及提交交易的功能。

管理 API 用于部署业务网络。

Composer playground

Hyperledger Composer playground 是一个基于浏览器的界面,用于创建和测试业务网络。你可以使用 playground 来构建和测试你的业务网络。

Composer-cli

Composer-cli 是一个命令行工具,可以让你部署和管理业务网络。

以下是一些命令的列表:

命令 描述
composer archive create 创建业务网络存档文件(nba)的命令。
composer archive list 验证业务网络存档的内容。
composer card create 从个别组件创建业务网络卡。
composer card delete 从个别组件中删除业务网络卡。
composer card list 列出存储在本地钱包中的所有业务网络卡。
composer network deploy 将业务网络存档从本地磁盘部署到 Hyperledger Fabric 网络。
composer network list 列出业务网络卡的详细信息。
composer network ping 测试已部署业务网络的连接。

设置 Hyperledger Composer 环境

我们刚刚审查了 Hyperledger Composer 解决方案体系结构。在本节中,我们将设置 Hyperledger 开发环境。

安装先决条件

在安装 composer 工具之前,请确保按照 Hyperledger Fabric 环境设置 - 安装先决条件一节来获取所需的先决条件。

安装开发环境

以下是开发环境安装命令:

  • 安装 CLI 工具:
 npm install -g [email protected]
  • 安装composer-rest-server
 npm install -g [email protected]
  • 安装 Hyperledger Composer 生成器:
 npm install -g [email protected]
  • 安装 Yeoman:
 npm install -g yo
  • 安装游乐场:
 npm install -g composer-playground
  • 安装 fabric 运行时:

下载并安装 composer 的 fabric 运行时如下:

 mkdir ~/fabric-devserver && cd ~/fabric-devserver
 curl -O https://raw.githubusercontent.com/hyperledger/composer- tools/master/packages/fabric-dev-servers/fabric-dev-servers.zip
 unzip fabric-dev-servers.zip
 export FABRIC_VERSION=hlfv12
 ./downloadFabric.sh

在这一步,你已经安装了典型的 composer 开发环境所需的一切。

分析业务场景

第十六章使用 Hyperledger Fabric 探索企业区块链应用程序中,我们讨论了对保险索赔的区块链用例。它包括以下步骤:

  1. 保险人向经纪人报告索赔

  2. 经纪人提供请求的信息

  3. 经纪人向发行者提交索赔

  4. 发行者确认索赔

  5. 发行者处理并批准索赔

在本章中,我们将使用相同的保险索赔用例,但也通过 Hyperledger Composer 构建端到端应用程序。

业务网络存档

Composer 业务由四种不同类型的文件组成:模型文件(.cto)、脚本文件(.js)、访问控制列表(ACL)文件(.acl)和查询文件(.qry)。

网络模型文件(.cto)

CTO 文件由以下元素组成:

元素 描述
单个命名空间 定义 composer 模型命名空间;每个.cto 模型文件都需要一个命名空间。
资源 - 资产 可以在各方之间交换的任何有价值的东西。
资源 - 参与者 业务网络成员。
资源 - 枚举 由一组命名值组成的数据类型。
资源 - 概念 你想要建模的任何对象,而不是其他类型的对象。
资源 - 交易 定义区块链业务逻辑。
Resources - events 区块链事务通知。
Import 从其他命名空间导入资源。

Composer 模型语言,就像其他编程语言一样,具有包括 String、Double、Integer 等数据类型。

让我们看一些资产、参与者、交易和事件的示例。

IBM Bluemix 提供了一个无需安装的浏览器版本 Playground;我们可以使用这个工具快速进行原型设计。这是链接:composer-playground.mybluemix.net/

连接到基本样本网络。 Playground 将为您生成一些默认的示例资产、参与者、交易和事件,例如:

sample.cto
/**
* Sample business network definition.
 */
namespace org.example.basic
asset SampleAsset identified by assetId {
  o String assetId
  --> SampleParticipant owner
  o String value
}
participant SampleParticipant identified by participantId {
  o String participantId
  o String firstName
  o String lastName
}
transaction SampleTransaction {
  --> SampleAsset asset
  o String newValue
}
event SampleEvent {
  --> SampleAsset asset
  o String oldValue
  o String newValue
}

sample.cto.SampleAsset 中定义了一个 namespace org.example.basic,这是一个 Asset 类的示例。它定义了一个资产,其名称后跟着一个标识性的 field.o String assetIdSampleAsset 的一个字段。--> SampleParticipant owner:字段指向 SampleParticipant instance.SampleParticipantParticipant 类的一个示例,语法与 SampleAsset.SampleTransaction 是一个事务的示例 class.SampleEvent 是一个事件类的示例。

脚本文件 (.js)

我们在模型文件中定义了交易和事件,脚本文件实现了这些交易功能。注释中的装饰器用于用于事务处理所需的元数据注释函数,例如:

/**
 * Sample transaction processor function.
 * @param {org.example.basic.SampleTransaction} tx The sample transaction instance.
 * @transaction
 */
async function sampleTransaction(tx) {  // eslint-disable-line no-unused-vars
..
    emit(event);
}

sampleTransaction 函数中,@param 标签后跟着触发事务处理器函数的事务的资源名称。 @transaction 将此函数标记为事务处理器函数。

访问控制列表 (ACL) 文件 (.acl)

ACL 文件定义了业务网络中参与者的权限,例如:

rule OwnerHasFullAccessToTheirAssets {
    description: "Allow all participants full access to their assets"
    participant(p): "org.example.basic.SampleParticipant"
    operation: ALL
    resource(r): "org.example.basic.SampleAsset"
    condition: (r.owner.getIdentifier() === p.getIdentifier())
    action: ALLOW
}

在前面的 ACL 示例中,指定参与者为 SampleParticipant。任何注册为 SampleParticipant 的实例都可以对所有的 org.example.SampleAsset 实例执行 ALL 操作。此事务在 SampleAsset 的所有者与提交事务的参与者相同时触发。

查询文件 (.qry)

查询文件定义了用于返回关于区块链世界状态的数据的查询。查询语法与 SQL 语言非常相似,例如:

query queryName {
    description: "Select SampleAsset by assetId "
    statement:
        SELECT org.example.basic.SampleAsset
            WHERE (_$assetId = assetId)
}

设计业务模型

现在我们已经审查了基本的 Composer 模型语言和结构,是时候使用 Hyperledger Composer 实现一个保险理赔了。

为简单起见,我们将允许参与者在此示例中有权限读取和写入所有资源。删除与 ACL 相关的示例资源,并更新规则如下:

rule EverybodyCanReadEverything {
    description: "Allow all participants read access to all resources"
    participant: "**"
    operation: READ
    resource: "com.packt.quickstart.claim.*"
    action: ALLOW
}
rule EverybodyCanSubmitTransactions {
    description: "Allow all participants to submit transactions"
    participant: "**"
    operation: CREATE
    resource: "**"
    action: ALLOW
}

简化后的 ACL,我们开始按照以下方式处理我们的模型文件:

  1. sample.cto 重命名为 insurance-claim.cto

  2. 将命名空间更改为 com.packt.quickstart.claim 并删除其余代码

  3. 定义参与者和资产

我们在第十六章中编写了一个名为claimcontract.go的链码,使用 Hyperledger Fabric 探索企业区块链应用程序,该链码定义了被保险人、经纪人、保险人和索赔的结构。我们可以类似于这个结构定义参与者和资产。如下所示,这非常简单:

      namespace com.packt.quickstart.claim
      participant Insuree identified by id {
        o String id
        o String firstName
        o String lastName
        o String ssn
        o String policyNumber
      }
      participant Company identified by id {
        o String id
        o String type
        o String name
      }
      asset Claim identified by id {
        o String id
        o String desc
        o Integer status
        o String insureeId
        o String brokerId
        o String insurerId
        o String comment
        o String processAt
      }
  1. 定义交易和事件。通过使用Init函数,我们登记被保险人,如下所示:
      transaction Init {
        o String insureeId
        o String firstName
        o String lastName
        o String ssn
        o String policyNumber
      }
      event InitEvent {
        --> Insuree insuree
      }
  1. Composer 的 JavaScript API 提供了用于创建资源(包括参与者)的 CRUD。对于保险人和经纪人,我们将使用这种方法。我们在进行测试时会更详细地解释这一点。

  2. 定义ReportLost:被保险人向经纪人报告索赔—这启动了一个索赔,如下所示:

      transaction ReportLost {
        o String claimId
        o String desc
        o String insureeId
        o String brokerId
      }
      event ReportLostEvent {
         --> Claim claim
      }
  1. 定义RequestedInfo:经纪人提供请求的信息,如下所示:
      transaction RequestedInfo {
        --> Claim claim
      }
      event RequestedInfoEvent {
        --> Claim claim
      }
  1. 定义SubmitClaim:经纪人向发行人提交索赔。

  2. 定义ConfirmClaimSubmission:发行人确认索赔。

  3. 定义ApproveClaim:发行人处理并批准索赔。

步骤 8、9 和 10 是交易函数,与步骤 7 非常相似。

我们在模型文件中定义了所有的交易、参与者和资产。作为下一步,我们将实现模型文件中定义的交易。

实现业务交易函数

我们通过审查SampleTransaction在前一节学习了如何实现交易函数。按照类似的方法,我们将实现一个保险索赔交易函数。将sample.js重命名为logic.js

实现Init函数,如下所示:

Init() function is used to register insuree person information.
/** 
  * Create the insuree
  * @param {com.packt.quickstart.claim.Init} initalAppliation - the InitialApplication transaction
  * @transaction
  */
 async function Init(application) { // eslint-disable-line no-unused-vars
     const factory = getFactory();
     const namespace = 'com.packt.quickstart.claim';
     const insuree = factory.newResource(namespace, 'Insuree', application.insureeId);
     insuree.firstName = application.firstName;;     insuree.lastName = application.lastName

     insuree.ssn = application.ssn;;
     insuree.policyNumber = application.policyNumber;;
     const participantRegistry = await
getParticipantRegistry(insuree.getFullyQualifiedType());
     await participantRegistry.add(insuree);
     // emit event
     const initEventEvent = factory.newEvent(namespace, 'InitEvent');
     initEventEvent.insuree = insuree;
     emit(initEventEvent);
 }

实现ReportLost,设置并创建索赔,如下所示:

/**
  * insuree report lost item
  * @param {com.packt.quickstart.claim.ReportLost} ReportLost - the ReportLost transaction
  * @transaction
  */
 async function ReportLost(request) {
     const factory = getFactory();
     const namespace = 'com.packt.quickstart.claim';
     let claimId = request.claimId;
     let desc = request.desc;
     let insureeId = request.insureeId;
     let brokerId = request.brokerId;
     const claim = factory.newResource(namespace, 'Claim', claimId);
     claim.desc = desc;
     claim.status = "ReportLost";
     claim.insureeId = insureeId;
     claim.brokerId = brokerId;
     claim.insurerId = "";
     claim.comment = "";
     claim.processAt = (new Date()).toString();
     const claimRegistry = await getAssetRegistry(claim.getFullyQualifiedType());
     await claimRegistry.add(claim);
     // emit event
     const reportLostEvent = factory.newEvent(namespace, 'ReportLostEvent');
     reportLostEvent.claim = claim;
     emit(reportLostEvent); }

实现RequestedInfo以验证和更新索赔状态,如下所示:

/**
  * broker send Requested Info to insuree
  * @param {com.packt.quickstart.claim.RequestedInfo} RequestedInfo - the RequestedInfo transaction
  * @transaction
  */
 async function RequestedInfo(request) { // eslint-disable-line no-unused-vars
     const factory = getFactory();
     const namespace = 'com.packt.quickstart.claim';
     let claim = request.claim;
     if (claim.status !== 'ReportLost') {
         throw new Error ('This claim should be in ReportLost status');
     }
     claim.status = 'RequestedInfo';
     claim.processAt = (new Date()).toString();
     const assetRegistry = await getAssetRegistry(request.claim.getFullyQualifiedType());
     await assetRegistry.update(claim);
     // emit event
     const requestedInfoEventEvent = factory.newEvent(namespace, 'RequestedInfoEvent');
     requestedInfoEventEvent.claim = claim;
     emit(requestedInfoEventEvent); }

实现SubmitClaimConfirmClaimSubmissionApproveClaim。这些功能与RequestedInfo类似。

在游乐场进行测试

我们刚刚在前一节中实现了所有的模型和逻辑文件,所以现在是测试我们的 composer 应用程序的时候了:

  1. 单击游乐场左下角的部署更改按钮。这将部署 composer 代码。

  2. 点击顶部导航栏上的测试链接。将弹出提交交易页面。从交易类型下拉菜单中选择init方法。输入 JSON 值,如下截图所示;输入数据与我们在第十六章中测试的相同,使用 Hyperledger Fabric 探索企业区块链应用程序。实例化面料链码步骤。提交交易,如下所示:

如果交易提交成功,我们将能够看到被保险人参与者已创建,例如:

  1. 现在,让我们入职经纪人和保险人。在参与者部分中点击公司,然后点击创建新参与者。输入经纪人数据,方式与我们在第十六章中的chaincodeInvokeAddBroker步骤相同,使用 Hyperledger Fabric 探索企业区块链应用程序。点击创建新,如下所示:

如果交易提交成功,这将入职经纪人。重复相同步骤以入职保险人,如下所示:

  1. 提交ReportLost,如下所示:

这是结果:

  1. 用以下结果测试RequestedInfo

剩下的SubmitClaimConfirmClaimSubmissionApproveClaim步骤与RequestedInfo非常相似。

部署业务网络

我们在游乐场中测试了 composer 应用程序,接下来我们将把它部署到区块链上:

  1. 创建一个名为insurance-claim-network的文件夹,并导航到该文件夹。

  2. 生成业务网络项目模板,如下所示:

 yo hyperledger-composer:businessnetwork

它会提示几个问题。输入insurance-claim-network作为网络名称,并选择空模板网络,如下截图所示:

这将生成一些具有默认模板的文件。替换com.packt.quickstart的内容,如下所示:

用我们之前测试过的模型文件.claim.cto

创建一个名为lib的新文件夹,在lib文件夹下,将测试过的logic.js复制到这里。

用被测试过的acl文件替换permissions.acl,如下所示:

  1. 启动 Hyperledger Fabric,如下所示:
 cd ~/fabric-devservers
 export FABRIC_VERSION=hlfv12
 ./startFabric.sh
 ./createPeerAdminCard.sh

这将创建PeerAdminCard,如下截图所示:

  1. 生成业务网络档案。从insurance-claim-network目录中运行以下命令:
 composer archive create -t dir -n 

这将生成[email protected]

  1. 安装业务网络。从insurance-claim-network目录中运行以下命令:
 composer network install --card PeerAdmin@hlfv1 --archiveFile 
      [email protected]
  1. 启动业务网络。从insurance-claim-network目录中运行以下命令:
 composer network start --networkName insurance-claim-network --
      networkVersion 0.0.1 --networkAdmin admin --networkAdminEnrollSecret 
      adminpw --card PeerAdmin@hlfv1 --file networkadmin.card
  1. 导入网络管理员卡。从insurance-claim-network目录中运行以下命令。这将会将insurance-claim-network导入到网络中:
 composer card import --file networkadmin.card
  1. 检查业务网络是否已成功部署。从insurance-claim-network目录中运行以下命令:
 composer network ping --card admin@insurance-claim-network

结果应如下所示:

检查业务网络是否已成功部署

与 REST 服务器集成

我们刚刚在 fabric 网络中部署了insurance-claim-network。下一步是构建一个保险索赔客户端 API,与网络中的智能合约函数进行交互。Hyperledger Composer REST 服务器可用于生成 REST API。REST 客户端可以调用这些端点函数,并与 Fabric 区块链上的业务网络链码交互。

生成 Hyperledger Composer REST API

运行以下命令生成 composer 服务器 API:

composer-rest-server

从业务网络卡中输入admin@insurance-claim-network,如下截图所示:

输入业务网络卡

这将生成 REST API,将其暴露为http://serverIP:3000http://serverIP:3000/explorer

打开浏览 URL。你会看到生成的 REST 端点,如下所示:

让我们尝试多种方法来演示这些端点是如何与 fabric 网络进行交互。

从端点中选择init Post方法,并提供 post JSON 数据,然后点击 Try it out! 按钮。JSON 数据示例如下所示:

{
  "$class": "com.packt.quickstart.claim.Init",
  "insureeId": "user-001",
  "firstName": "John",
  "lastName": "Smith",
  "ssn": "9999",
  "policyNumber": "string"
}

这是点击 Try it out! 按钮后显示的结果截图:

JSON 数据示例

API 将调用 fabric 网络中的Init链码,并将响应返回给浏览器。

使用 post 方法选择一个公司来创建被保险人。输入以下 JSON 请求如下:

{
  "$class": "com.packt.quickstart.claim.Init",
  "insureeId": "user-001",
  "firstName": "John",
  "lastName": "Smith",
  "ssn": "9999",
  "policyNumber": "string"
}

你应该会看到一个成功的返回,类似于下面截图中展示的:

使用 post 方法选择一个公司来创建被保险人,输入以下 JSON 请求

从端点中选择ReportLost Post方法,并提供 post JSON 数据,然后点击Try it out!:

{
  "$class": "com.packt.quickstart.claim.ReportLost",
  "claimId": "claim_001",
  "desc": "I was in Destiny shopping center and lost my IPhone 8",
  "insureeId": "user_001",
  "brokerId": "broker_001"
}

您应该会从区块链中得到一个成功的响应。

要验证索赔是否成功在网络中创建,您可以选择索赔 get 方法并点击 Try it out! 您应该能够获取到索赔结果,如下所示:

验证网络中索赔是否成功创建

其他保险索赔端点 API 将会和我们探索过的很相似。

总结

我们已经到达本章末尾。在这一章中,我们概述了 Hyperledger Composer 并安装了相关工具。我们使用 composer 模型语言开发了同样的保险索赔用例,就像第十六章中所探索过的使用 Hyperledger Fabric 探索企业区块链应用,并将其部署到 fabric 网络中。最后,我们将应用与 composer REST 服务器集成,生成客户端 API,并从 web 中与这些 API 进行交互。

到这一步,你应该已经熟悉了 Hyperledger Composer 的工作方式。现在我们已经到了本章的结尾,我们已经了解了两种最流行的公共和企业级区块链。作为一名区块链开发者,你应该具备基本的区块链知识,以便能够编写你自己的区块链应用程序。在下一章中,我们将讨论各种真实世界的区块链使用案例。

第十八章:区块链使用案例

完成前六章后,你应该已经掌握了足够的知识,可以思考如何将你新获得的技能应用于解决现实生活中的问题。正如之前讨论的那样,区块链被认为是一种颠覆性的技术,可以通过使中间服务过时来潜在地破坏现有的商业模式,并激发新的、具有成本效益的商业模式的创造。然而,这项技术无法解决所有问题,其价值只有通过与其他成熟或新兴技术结合使用才能充分实现,例如大数据平台、云计算、数据科学/人工智能和物联网。

在本章中,我们首先高层次地讨论了跨行业流行的区块链使用案例,包括金融、公共服务、供应链、物联网和医疗保健。然后,我们将讨论考虑因素,确定适当的使用案例,并开发成功的 DApp。最后,我们以健康数据共享使用案例为例,对构建其 DApp 进行高层次评论。具体而言,我们涵盖以下主题:

  • 区块链使用案例示例

  • 如何选择适当的使用案例

  • 对医疗数据共享使用案例的深入讨论

区块链应用案例示例

技术的发展从根本上改变了人们的生活。在人类历史上,机器曾经取代人类执行各种任务。例如,在农业领域,农用车辆使农业工作变得不那么劳动密集,并大大提高了生产力。截至 2008 年,在美国,直接从事农业工作的人口不到 2%。他们不仅供应其他 98%人口所需的食物,还使美国成为最大的农产品出口国。其他领域的例子包括可编程电话交换机替代电话操作员,以及自动电梯替代电梯操作员。

机器取代人类的趋势在过去的几十年里加快了,这在很大程度上是由于计算机的发明。到目前为止,技术领域已经出现了三次以计算机为主导的革命。每一次革命都从根本上影响了现有的商业模式,并激发了新的商业方式。我们现在正处于第四个阶段的黎明:由区块链技术主导的革命。以下是各个阶段:

  • 主机和个人电脑的发明:计算机以更快、更好地执行重复计算取代了人类。计算机驱动自动化的应用无处不在,数量巨大。

  • 互联网:互联网指的是全球互联的计算机网络。互联网的到来从根本上改变了服务的提供方式。比如,在 90 年代,在本地租赁商店租一盘录像带或 CD/DVD 是一个周末的热门家庭娱乐活动。Blockbuster 是一个提供租赁服务的家喻户晓的品牌,其商业模式运转良好。到 2004 年,Blockbuster 在全球雇用了 84300 名员工,总共拥有 9094 家门店。有了互联网,新公司如 Netflix 崛起并打破了 Blockbuster 等公司的霸主地位。家庭不再需要去实体店拿 CD/DVD。相反,他们可以在互联网上下载电影的虚拟副本。2010 年,Blockbuster 申请破产。如今,Netflix 是一家市值 1450 亿美元的公司。这样的故事在其他领域也反复出现过很多次。

  • 社交媒体网站:诸如 Facebook、Twitter 或 YouTube 等社交媒体网站不仅改变了现有的商业模式,还改变了人们获取新闻以及新闻传播方式。纸媒和基于广播/电视的新闻传播不再是新闻传播的唯一渠道。许多新闻发布者由于失去订阅者而不得不关闭。另一方面,自由撰稿的新闻记者开始通过 YouTube 等方式出现。社交媒体网站也从根本上影响了政府,因为新闻审查变得更加困难。

第 4 阶段——区块链技术:即使在社交媒体网站上,审查制度——尽管更加困难——仍然可能存在,因为信息是托管和处理在一个集中的服务器上,所以审查可以在那个集中的服务器上实施。而在区块链网络中,由于区块链技术的去中心化设计,审查就不太实际。唯一的方法就是关闭国内网络的所有节点。区块链对现有商业模式的最大影响来自于这项技术将使中介服务过时。它将特别影响到金融行业,其中大部分金融服务本质上是中介服务。这项技术即将带来的变革不仅仅局限于金融行业,而且几乎对每个其他行业都会产生影响。

接下来,我们列举了一些潜在的区块链技术应用的例子。我们使用了潜在这个词,因为这项技术仍在不断发展,并且其当前形式有许多限制。没有保证这些应用案例能被实施。许多这些应用案例要成为现实还需要一段时间,而有些可能永远无法实现。无论一个应用案例能否被实施,我们都应该把讨论重点放在业务问题上,以及利用这项技术解决业务痛点的想法上。随着技术的进步,比如性能的提高,一些应用案例将成为现实。我们首先讨论的是金融行业中的应用案例。

支付和结算服务

在传统方式下,银行之间的交易对账成本高昂且耗时。例如,2016 年仅美国就有 70 亿笔借记卡交易。同年,VisaNet(世界最大的电子支付网络之一)每天处理平均 1.5 亿笔交易。在这么高的交易量下,即使每笔交易节省极微,也会在企业运营总成本上产生巨大降低。在股票交易中,一笔交易加上结算需要三天的完整周期。在未能解决一笔交易对账时可能导致重大经济损失。(因此,像 DTCC 这样的清算中心实施一种保险机制来减轻结算风险。)通过区块链技术,支付处理正逐渐向全球即时支付发展。例如,Ripple 可以在几分钟内完成跨境支付。该技术结合了交易和结算。它大幅降低了相关交易成本。交易的步骤对请求者可见。最近的一份研究报告声称,Ripple 的支付成本仅占传统交易成本的 0.1%。同样,对于股票交易,由于区块链实施将交易和结算归为一体,不再存在结算风险。交易所会员公司不需要为结算保险支付溢价,也不需要一个专门处理结算的大后勤团队。这将显著降低公司营业成本。

进出口贸易融资

在商品交易中,进口商和出口商分别使用他们的银行发行信用证LC)并结算支付。区块链将使银行简化文件管理。它为参与方提供透明度,减轻了文件欺诈的潜在风险。它使银行之间和内部的交易对账变得更加简单,从而实现重大节省。去中心化账本提供可审计的交易日志,使法律纠纷不太可能发生,且更容易解决。

不可变账本

记账或记录方法,例如使用分类帐,由于参与者数量的增加和交易复杂性的增加而变得越来越复杂。维护分类帐的传统方式是集中式的。这种方法缺乏透明度,导致经常发生法律或非法的争端。还很难识别错误,因为参与交易的各方没有有效的实时方法来检查和验证交易事实与分类帐相对应。区块链技术可以无缝解决这些问题。在链上维护的分散和不可变的分类帐几乎消除了任何争端的机会,并在交易中为各方带来信任。区块链允许实时查询并允许各方确保其正确性。与传统方式不同,其中两个交易方分别在其各自的分类帐中保存条目,可能导致不一致,区块链分类帐允许所有参与方维护统一的分类帐,消除了不一致性的可能性。

监管合规和审计

凭借其不可变性,监管机构可以信任他们从记录在企业区块链上的交易中提取的任何信息。金融公司无需采取措施准备数据并大力投资于实施数据治理以确保数据的正确性。因此,区块链技术潜在地可以帮助金融公司降低监管合规和审计成本,涉及领域包括证券交易、反洗钱(AML)和了解您的客户(KYC)。

身份盗窃检测

分散的区块链系统阻止了银行盗窃和黑客活动。采用区块链技术将使身份盗窃的检测变得更加容易。如果一个小偷盗窃了身份并开设了银行账户或提出了欺诈性的税务要求,受影响的个人可以看到所有以其名字注册的账户,并识别出可疑活动。然后,这个人可以立即向银行或国税局报告,防止他们进一步遭受损失。

后勤后台操作

区块链可用于在新客户入职时提高满足 AML 和 KYC 法规要求的措施的效率。它可以帮助计算资金的净现值,以及其他后勤活动,如对账和处理公司行为(如股票拆分、公司并购等)。

抵押管理

在传统方式下,无论是双边交易还是三方交易,抵押品信息都无法实时提供给参与交易的各方。区块链可用于提供去中心化的抵押品管理系统。它提供了实时透明度,并维护了抵押品使用状态的一份副本,从而消除了由于各方保留其个人记录以及传统的抵押品管理方法而导致的抵押品信息不一致的可能性。

在前一节中,我们讨论了金融行业的用例示例。接下来,我们将涵盖金融行业以外的示例。

医疗系统

区块链可以帮助解决有效管理健康数据的问题。采用区块链技术可以简化医疗数据管理。例如,患者的病史、诊断信息和检测结果保存在各自的医生办公室。在医生之间共享医疗信息,例如患者的家庭医生和专家,是耗时且困难的。这可能导致诊断延迟或生成重复的医学测试。区块链可以使数据共享更容易,同时保护机密数据。然后可以对详细的医疗记录进行汇总。汇总的信息可以提供给医学研究人员、政府机构以及制药或保险公司。通过简化和全球范围的访问,健康数据共享有助于促进研究人员和制药公司在新治疗和药物开发方面的合作。基于真实的健康数据,政府机构可以制定改进的卫生政策。医疗保险公司可以利用数据计算计划的保费,并减少收集所需数据的成本。临床试验结果的去中心化也是如此。总之,区块链可以彻底改变健康数据的存储、管理和共享方式。它将深刻影响整个健康产业的发展。

房地产交易和租赁市场

在美国,房地产经纪人收取经纪费,通常为售价的 5%至 6%,用于将卖方和买方联系起来进行房地产交易。律师为买方或卖方提供法律服务,收取数百美元的费用。在纽约市,房地产经纪人通常会向客户收取一个月的租金,通常数千美元,以促成租赁交易。与提供的有限服务相比,这相当昂贵。区块链提供了一个成本更低的解决方案,并提供了透明度的附加价值。房地产区块链网络为不信任的买方/卖方或租户/房东进行配对交易。脚本化的法律文件,即智能合约,取代了房地产律师提供的大多数法律服务,因此使它们变得不必要。由于区块链将交易和结算合二为一,因此不需要第三方担保账户。换句话说,房屋所有权的转移和付款同时进行。在租赁房产的情况下,类似的解决方案也是适用的。这将导致双方在交易中实现大幅度的节省。面对区块链技术带来的威胁,房地产经纪人将不得不找到一种创新的方式来提供增值服务,或者转行从事其他职业。

知识产权市场

IP 指的是知识产权。这可以是数字或数字化资产,例如小说、歌曲、电影、绘画、专利或软件。区块链技术可以潜在地用于建立一个购买和出售 IP 的市场。这将允许所有者将 IP 资产出售给买方。例如,小说完成后,作者可以生成预定数量的数字副本,并直接销售给读者。每个数字副本都有一对公钥/私钥和一个地址。在支付书的价格后,此副本的所有权转移给买方。买方可以在未来在二级 IP 市场上转售此副本。这种新的业务模式不涉及出版商。作者可以从书籍销售中获得大部分收入。

选举

目前进行选举的方式有多个缺点。首先,它经常需要投票者亲自到场投票。许多选举仍然依赖于纸质投票。这使得计票非常耗时、劳动密集和昂贵。这也可能导致漫长的重新计票,就像之前美国总统选举中在几个险象环生的州发生的情况一样。重新计票需要数周甚至更长的时间才能完成。操纵结果、重复投票或伪造投票是其他经常被提及的问题,即使在其他国家的几次备受关注的选举中也是如此。

区块链技术可以用来解决这些问题。通过基于区块链的选举 DApp,投票可以在全球范围内进行。每个选民都被分配一个独特的账户、一个地址,使得双重投票变得不可行。选举结果是不可变的。因此,操纵选举结果是不可能的。该技术还带来了另一个优势,即使全球范围内的跨境公民投票成为可能,涉及的问题可以是环境主题等。

人力资源和招聘

大型公司人力资源部门面临的一个常见问题是如何识别具有正确技能、工作经验和教育背景的候选人。通常,解决方案是聘请专业的招聘人员或猎头,他们通过个人网络或通过扫描 LinkedIn 等社交媒体网站来识别候选人。支付给招聘人员的费用等于被聘用候选人一个月的工资或更多。区块链可以用于构建专业人士的分散数据库。这可以达到两个目的。首先,它提供了透明度,以匹配雇主和候选人。其次,每家公司都可以了解潜在员工的历史和该人员的当前就业状态。这有助于过滤掉潜在的欺诈者被公司雇佣。

公共记录

政府的市政行政办公室维护着各种类型的记录,例如公民、纳税申报、土地所有者和产权证书持有人、建筑许可证、分区信息、专利、水管线和污水布局等。这些记录需要持续更新。此外,这些办公室经常收到查询。将这些记录保存在纸上很昂贵,因为政府必须雇佣一组办公室职员来手动管理记录。即使记录是电子文件,也仍然需要手来回应查询。通过区块链托管的数字账本,可以通过软件查询工具而不是通过办公室职员来满足查询。这种解决方案将数据免受恶意篡改。这也有助于减少身份盗窃。

减少合同纠纷

区块链被 IBM 内部用于解决网络合作伙伴之间的合同纠纷。根据 IBM 的估计,其 290 万笔交易中平均有 0.9%导致纠纷。这些纠纷导致大约 1 亿美元的资本被束缚,无法投入运营以获利。1 亿美元的资本融资成本不容忽视。解决纠纷也会带来显著的成本。通过区块链解决方案,IBM 可以结合网络参与者提供的数据,创建所有交易的全面视图。区块链通过访问控制的治理机制提供了强大的隐私和机密性控制。该解决方案大大减少了纠纷数量。

共享经济

区块链技术可以用来创建一个市场,促进物品或服务的剩余价值共享或租赁给他人。例如,一个人可能有一台未充分利用的计算机,可以租给另一个需要临时增加计算能力的用户。在这种情况下,区块链共享市场可以用来完成这样的租赁安排。该平台基本上允许个人在几乎任何可以共享的东西上经营私人租赁业务。基于区块链技术的市场适用于任何使用可以方便地通过数字方式共享的设备,如计算机。市场也可用于促进一般服务的共享。例如,在一个欧洲国家,年轻人为老年人提供托儿服务,以换取积分。累积的积分可以在一个人变老时用来获得类似的服务。通过区块链解决方案,该人将获得为他人提供的服务的数字硬币,可以后来使用这些硬币来获得服务。这可能是解决美国托儿保育成本高的问题的一种解决方案。

与物联网的整合

物联网指的是物联网。物联网是许多不同类型的事物的网络,例如物理设备、车辆、家用电器和传感器。这些物品连接在一起,可以收集和共享数据。当结合区块链技术和物联网时,我们可以潜在地实施许多有意义的应用。它们可以有很多好处,比如提供人们生活的便利性,拯救生命,以及降低经营成本。以下是一些可能的用例:

  • 当嵌入式传感器检测到洗涤剂的水平低于预设水平时,可以自动调用智能合约来订购额外的洗涤剂。

  • 一个健康手环或嵌入式传感器在衣物中可能会检测到一个人的重要统计数据,指示可能发生心脏病的可能性。然后,它会自动触发一个智能合约,向药店发送订单,并通过短信或电子邮件向个人发送警报。当这种应用实施得当时,它可以拯救许多生命。

  • 冰箱内置传感器可以检测蔬菜或肉类的数量,并触发智能合约从当地农民和肉类供应商那里订购额外的蔬菜/肉类,而不需要超市参与。这将避免商店对食品增加的成本,并为消费者节省开支。

  • 当酒店客人准备退房时,客人只需在指定地点放下房间钥匙(或智能钥匙)。传感器触发结账智能合同。触发后,智能合同将访问通过房间其他传感器收集的数据,例如迷你吧中零食/饮料的消费信息或房间设施可能的损坏信息。根据收集的数据,智能合同将计算最终金额,并通过调用和完成付款交易完成客人的结账。有了这样的解决方案,客人无需前往酒店前台,这节约了客人的时间。这也有助于酒店减少运营成本,因为酒店无需雇佣员工在前台值班。

促进商业和社交关系

澳大利亚政府实体、CSIRO(澳大利亚联邦科学与工业研究组织)进行了为澳大利亚利益进行科学研究。该机构指出,区块链可以用作促进商业和社交关系的数据库系统。区块链技术的有效应用在于与多个组织互动的复杂市场。

如何选择合适的使用案例

鉴于对区块链技术及其对现有商业模式潜在影响的诸多猜测,现在是现实主义的时候了。在高德纳最新的新兴技术曲线报告中,区块链被称为进入了公司曲线炒作评估的第三阶段—幻灭谷阶段,如下截图所示:

在 2017 年第四季度和 2018 年第一季度之间,1 BTC 的价格曾一度超过 19K,然后在几天内迅速下跌至 10k。截至 2018 年 12 月 11 日,价格为 3.4K。比特币价格的这种惊人过山车式起伏导致了对加密货币的疯狂炒作的降温,进而减少了对加密货币项目的投资。此外,技术的局限性也是一个因素,使得技术不适用于某些使用案例。众所周知的问题是大多数区块链平台从比特币继承的每秒交易量TPS)很低,如在第十三章的讨论中所述,以太坊基础。成功的区块链应用仍然稀少,主要集中在金融行业。瑞波是一个成功的案例,专注于现金支付。

由于区块链技术并不适用于所有使用案例,在开始行动之前选择一个合适的使用案例非常重要。以下评论可能有助于帮助您确定要处理的使用案例以及如何选择用于其实施的区块链平台以及其他架构考虑因素:

  • 并不是每一个用例都适合使用区块链。例如,许多用例可以使用传统技术实现。的确,区块链是一个数据存储库。如果唯一目的是托管数据,选择普通数据库可能足够了。目前写入区块链仍然比写入数据库要慢得多。插入到区块链需要几秒钟或几分钟。插入到数据库只需要几毫秒。这使得数据库在许多需要高吞吐量的用例中更好的选择,例如捕获信用/借记卡交易或股票交易市场数据。未来,随着性能和可扩展性的提高,区块链技术可以用于这些用例。

  • 在物联网用例中,需要考虑如何将物联网设备与区块链网络集成。物联网设备不是计算机。因此,物联网设备不能是区块链网络的节点。一个可能的解决方案是通过 API 将设备与网络上的节点连接起来。节点与区块链分类帐进行交互,并在接收到物联网设备信号时触发相应的智能合约。性能也是一个问题。一些物联网设备,如飞机传感器,会产生高频测量。低 TPS 的区块链网络无法快速响应这些设备的请求。

  • 区块链平台的区块大小是有限的。例如,比特币的区块大小限制在大约1 MB。以下图表(来自blockchain.com)展示了其截至2018 年 10 月的平均区块大小历史。对于像出售小说或电影的 IP 市场这样的用例,对数字资产的详细信息需要大量存储空间。可以考虑将链上和链下存储结合起来设计以解决区块大小受限的问题。数字资产的详细信息可以在集中的地方链外保存。以太坊已经采用了链上和链下数据存储方法:

  • 如果一个用例涉及到数字化资产,需要解决一些关于管理底层物理资产的问题:

    • 实物资产需要公证以证明其真实性。

    • 需要一个解决方案来确保底层资产在被公证和转让其所有权之间保持不变。

    • 与数字货币的双重支付问题类似,需要一个解决方案来确保一个实物资产映射到一个且仅一个数字资产。

    • 有价值的实物资产需要安全的存放地点。当所有权在数字上转移时,需要记录和转移底层资产的相应所有权。一个可能的解决方案可以从黄金交易中借鉴。物理黄金可以存放在一个安全的地方,比如纽约联邦黄金库。当一堆黄金条的所有权发生变化时,物理黄金条并不离开保险库。

  • 尽管以太坊是一个支持 DApp 开发的通用平台,但其实现涉及数字货币。每当加密货币是解决方案的一部分时,可能需要处理与之相关的法律复杂性。例如,在美国,BTC 被定义为资产,而不是货币。换句话说,当一笔 BTC 被卖给买家时,会产生税务影响(比如销售税)。某些国家如中国禁止加密货币交易。因此,可能更喜欢企业区块链解决方案,如 超级账本HF),因为它的实现不涉及加密货币。

  • 许多用例,如医疗数据共享或信用数据数字化,并不适合公共使用。需要基于许可的企业(或私有)区块链,而不是像以太坊这样的公共区块链。

  • 与以太坊相比,HF 或 R3 的 Corda 这样的私有链的另一个优势是,HF 和 Corda 都支持 Java 开发,而以太坊则要求程序员学习一种新的语言如 Solidity。鉴于 Solidity 的人才稀缺,要找到合格的开发者是困难且昂贵的。另一方面,将 Java 程序员转变成 HF 或 Corda 开发者可能是一个更容易的解决方案。

  • 区块链技术意味着所谓的智能合同,也就是脚本化的法律文件的保证执行,这使得不信任的交易方感到放心。如果一个用例不需要担保交易,那么它就不是一个适合的用例。例如,区块链不需要取代传统的网络约会网站。约会是非常个人化的,而且不会导致担保交易——比如婚姻。

  • 如果区块链仅被用作分布式账本,由于与区块链解决方案相关的成本,这是不合理的。如果容错性和透明性是主要目标,可以通过在多个节点上制作相同的账本副本来在传统方式下实现分布式账本,而不需要涉及比特币的挖矿和共识机制等额外组件。共识组件是为了解决双花问题而引入的。分布式账本不涉及双花问题。换句话说,如果只需要分布式账本,区块链就是画蛇添足。

  • 目前,区块链对于许多需要高吞吐量的用例仍然不是一个合适的解决方案,比如股票交易或信用卡交易。现有的区块链平台的速度要比其他平台(比如传统数据库)慢得多,用于托管交易数据。例如,需要专门的数据库 KDB 来保存每天数十亿条记录的市场数据。

  • 由于智能合同是脚本化的法律文件,还有待应对的法律挑战:

    • 智能合约运行时是否适用本地法律?如果答案是肯定的,那么当合同在节点的某些位置合法而在其他位置不合法时,如何处理与本地法律的冲突?

    • 区块链和加密货币的法规和法律尚未充分发展。最近,一名美国立法者敦促美国国内税务局澄清区块链的法规。

    • 由于智能合约的执行是自动的且无法阻止的,当无法充分解决非法活动(如洗钱)等问题时,区块链应用可能被判定为非法。

    • 加密货币在不同国家获得不同的地位。例如,在美国被定义为资产,在新加坡被视为货币。

DApp 的用例 - 医疗数据共享

在本小节中,我们将更详细地讨论一个用例,并谈论导致 DApp 实施的步骤。将进一步研究医疗数据共享的用例。这里仅讨论了想法,并不一定可实施。大部分讨论集中在业务和架构考虑方面。

业务问题

在着手实施 DApp 之前,应从业务问题开始,例如提出什么是挑战或痛点之类的问题?在医疗数据的情况下,挑战的示例如下:

  • 数字化:许多患者的医疗记录只能以纸质形式获得。对于家庭医生办公室来说,情况尤为明显,因为这些办公室通常规模较小。当患者访问医生办公室时,医生办公室的前台助手仍然经常需要在文件柜中搜索并拉出患者的医疗历史文件夹。然后,这些记录被交给医生。医生在与患者交谈时阅读这些记录。这种方法不具可扩展性且风险较大。自然灾害,如洪水或火灾,很容易毁坏这些记录。当患者更换医生时,旧的记录不会被转移。新的医生办公室将建立一个新文件夹,并开始积累患者的医疗史。由于旧记录的丢失,可能需要重新进行一些医学检查,导致患者额外的费用和不便。更重要的是,历史的丢失可能会导致失去本应用于治疗疾病的宝贵时间。

  • 及时性:由于患者的医疗记录在多个办公室物理维护,例如患者的家庭医生和专科之间共享记录是困难且耗时的。为了方便共享记录,患者首先将他/她的医生联系信息提供给专科办公室。然后,专科办公室接待员联系医生办公室。医生办公室安排通过传真或常规邮件发送信息。这种方法缓慢,昂贵且不安全。患者的医疗信息在信息传输期间可能被未经授权的人看到,而被盗的保险信息可能被用于恶意目的。

  • 所有权:医疗记录是患者的健康史。患者应该是医疗数据的所有者。医生办公室只是托管人。实际上,情况很少是这样。维护医疗记录的人成为事实上的所有者,并决定数据的使用或访问方式。

  • 透明度:由于医疗记录是纸质的,并分散在医生办公室,个人和机构用户,如医学研究人员,政府机构和保险公司没有方便的方式来访问聚合的医疗信息,以进行立法和其他目的。访问不涉及个人机密信息的聚合医疗信息,对于推动医学研究,优先考虑药物开发或制定政府卫生政策可能是有益的。

区块链解决方案

在确定业务问题及其痛点后,下一步是寻找合适的解决方案。对于前面的业务问题,需要一个通用解决方案来构建基于计算机的医疗数据共享平台。该平台将允许授权方,如医生,研究人员,政府机构,保险公司和制药公司访问医疗数据。开发这样的平台需要大量的工作。仅对现有纸质记录进行数字化就远远超出了我们的能力范围,并且需要许多团体和组织的参与。由于本书侧重于区块链技术,我们将集中精力解决方案的区块链部分,而不太担心其实际实施的可行性。

提议将区块链技术与其他技术结合,如大数据平台和数据科学。区块链将用于托管交易。大数据平台提供足够的空间,以在详细和聚合级别托管大部分医疗数据。基于数据科学的分析组件计算聚合的医疗数据并得出分析摘要。

  • 数据仓库:患者医疗数据的大小很容易达到几 TB。仅仅在链上托管医疗数据是不可行的。选择将链上和链下记录结合保存详细的医疗数据是合理的。事实上,以太坊区块链已经使用了在链下维护状态变量的想法,同时将交易和智能合约保存在链上。患者的医疗信息可以保存在链下,其哈希保存在链上。在这里使用哈希是为了防止未经授权修改医疗记录。每个患者的医疗记录都将被分配一个地址。当患者的医疗记录更新时,在链上生成一个类型为update的交易。生成新哈希对应于患者更新的医疗记录,并保存在链上。更新后的记录将有一个新的地址。类似地,当用户访问患者的医疗记录时,在链上保存一个类型为access的交易。这些交易的数字资产就是医疗记录。

  • 区块链平台选择:选择适当的区块链平台是一个重要的架构决策。需要考虑技术和非技术因素。一个关键的非技术考虑因素是如果一个 DApp 解决方案涉及加密货币,则法律影响如何,因为不同的国家对加密货币交易有不同的法律。试图遵循这些法律发行基于币的 DApp 是一项艰巨的任务,不值得去做。我们 DApp 的目的是解决一个业务问题,而不是发行数字货币。通用的公共区块链平台(如以太坊)涉及数字货币或代币。企业区块链平台(如 Hyperledger Fabric)不涉及加密货币。因此,HF 应该被考虑。由于 HF 是基于许可的,其共识算法不需要繁重和冗长的计算。因此,HF 提供了更高的 TPS。它还包含一个访问权和控制组件,用于管理医疗信息访问。

  • 分析组件:需要一个分析组件来执行诸如聚合详细的医疗信息和提供有用统计信息等任务。聚合是强制性的,以掩盖机密个人信息,并使医疗信息可被用户(如医学研究人员、政府机构或保险/制药公司)使用。患者的详细医疗信息只应在患者同意的情况下由患者的医生使用。聚合数据应至少使任何患者的信息无法被逆向工程出来。

  • 数据保护:对于任何 DApp 实现来说,保护平台上托管的数字资产是一个关键要求。有许多例子显示黑客攻击区块链平台、加密货币交易所或钱包,并窃取数百万美元的数字货币。一些著名的事件有几年前 Mt Gox 和 Bitfinex 的被黑客攻击。最近的一个例子是针对 Zaif 的攻击。此外,法律要求保护患者的隐私。不遵守隐私法将导致 DApp 应用的关闭,并产生昂贵的法律诉讼。为了保护患者的医疗信息,我们可以对链外数据进行加密。当患者授权医生访问医疗记录时,将提供一个临时密钥。医生办公室使用临时密钥获取一个用于解密记录的掩码私钥。私钥随后不应对医生办公室可见。只有临时密钥是可见的,并且在短时间内有效。下一次访问请求将生成一个新的临时密钥。汇总信息可能不需要加密。但是,根据明确定义的认证和获取模型,对信息的访问受到严格控制。

  • 后端组件:后端组件指的是智能合约开发。需要多个合约来提供规定交易的规则,包括上传医疗记录、访问详细医疗记录或查询汇总数据。由于这些交易在链上进行,它们提供了数据上传以及数据访问的审计迹象。通过这些审计迹象,患者可以轻松监控其医疗信息的使用,并有效保护其隐私和个人健康信息。

  • 前端组件:为了完成一个 DApp,需要一个前端组件。该组件包括与用户的界面以及与 HF 账本或分析组件的交互。与用户的接口允许用户,如医生办公室的接待员,上传或更新医疗记录。它还包含 GUI 工具,供用户访问详细和汇总的医疗信息。任何访问医疗信息的请求首先通过授权验证模块。在请求通过权限检查后,请求将被发送到模块,这些模块与 HF 节点交互以触发相应的智能合约并执行请求。这些模块还与区块链网络互动以上传数据。

下图显示了用户如何与健康数据共享平台组件互动:

用户与健康数据共享平台组件的互动

前端和后端组件的实施需要对区块链和 GUI 开发有深入的了解。仅凭 IDE 的准备和设置就不是一件容易的任务。有几家初创公司介入填补这些空白,使这些任务变得更加容易。有了这些工具,用户不再需要编写代码来执行重复的任务,如环境设置、测试和部署。相反,用户只需点击几下按钮。因此,开发人员可以专注于解决真正的业务问题。

  • 涉及的各方: 该平台涉及多个参与方。 医生办公室是主要的数据上传者,也是病人详细医疗记录的用户。 医学研究人员、政府机构、制药公司和保险公司是聚合医疗信息的用户。

  • 架构图:下面的架构图显示了实施医疗数据共享平台的分层设计。顶层包含前端组件。中间层用于离线数据处理和分析。底层是包含智能合约和其他 HF 组件的后端组件:

  • 项目资金:需要筹集足够的资金来支持医疗数据共享项目。一个想法是建立一个区块链初创公司,并游说潜在投资者资助努力。如今,由于新兴技术和在线服务的出现,建立和运行初创公司变得更加容易。例如,像 Linode 这样的云计算供应商提供了初创公司所需的经济实惠的硬件供应。同样,通过利用微信,初创公司的成员可以进行全球范围的可视会议,而不产生任何费用。Google Drive、Slides、Docs 和 Sheets 可作为虚拟团队的文档协作和共享软件。GitHub 用于软件共享和版本控制。对于财务审计,像 BitAudit(www.bitaudit.vip/)这样的公司专门为区块链技术公司提供审计服务。鉴于加密货币和区块链技术的法律尚未成熟且仍在发展中,这是有帮助的。

概要

与九十年代的互联网情况一样,区块链正处于区块链时代的黎明时刻。这项技术将颠覆现有的商业模式,并催生新的模式。它将激发建立在区块链网络上的社区经济的出现,其中每个参与者都为社区做出贡献,并从中获益。不再有单一实体控制并从经济中获得股息。

在本章中,我们讨论了跨行业的使用案例,以便让您了解潜在的区块链应用。鉴于现有技术的限制,并非所有这些案例都能立即实施。随着技术的进步,将能处理更多的使用案例。最后,我们讨论了在选择适当的使用案例时需要考虑的重要因素,以及通过对医疗数据共享使用案例的深入讨论来开发完整 DApp 的步骤。

标签:Fabric,网络,dev,交易,merge,应用程序,区块,我们,hplg
From: https://www.cnblogs.com/apachecn/p/18169370

相关文章

  • adv-bc-dev-merge-0
    区块链高级开发教程(全)原文:zh.annas-archive.org/md5/64e2728fdd6fa177d97883a45d7dec42译者:飞龙协议:CCBY-NC-SA4.0前言区块链技术是一种分布式分类账,应用于金融、政府和媒体等行业。本学习路径是您构建使用以太坊、JavaScript和Solidity构建区块链网络的指南。您将首......
  • eth-proj-bg-merge-0
    面向初学者的ETH项目(全)原文:zh.annas-archive.org/md5/5b197ea4ae8836b6854907e58ea8a1dc译者:飞龙协议:CCBY-NC-SA4.0前言本书旨在让你深入了解以太坊区块链世界,并让你使用以太坊制作自己的加密货币。在本书中,你将学习各种概念,并直接应用这些知识,同时还将介绍以太坊区块......
  • Audio Output Devices API
    AudioOutputDevicesAPI:音频输出设备API允许Web应用程序提示用户应使用什么输出设备进行音频播放。<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"/><metaname="viewport"content="width=device-width,i......
  • 使用浏览器无密码登录Azure DevOps Server
    1.概述AzureDevOpsServer作为软件开发管理平台服务器,除了支持主流的IDE开发工具外,普通用户使用最频繁的客户端工具是网页浏览器。由于AzureDevOpsServer集成AD域服务器实现用户认证,用户每次使用浏览器登录服务器时都需要输入用户的域账户和密码。但是,对于已经使用域账户登录......
  • Devexpress GridView 单元格输入检验
    实现效果打开设计器找到CellValueChanged事件编写代码privatevoidgvmain_CellValueChanged(objectsender,DevExpress.XtraGrid.Views.Base.CellValueChangedEventArgse){stringseq=gvmain.GetRowCellValue(e.RowHandle,colQaSeq).......
  • Offline web technology for Mobile devices
    一、当前问题1、弱网或断网,当用户进入电梯或无网区,长时间白屏或无法显示页面2、正常网络,从点击app到显示首页文字图片,白屏时间大约7-9秒 二、原因分析1、从技术视角,分析一下网页启动的几个关键耗时阶段 2、没有做离线化技术,而手机端用户进入弱网与无网区......
  • 界面组件DevExpress Blazor UI v23.2 - 网格、工具栏功能全新升级
    DevExpress BlazorUI组件使用了C#为BlazorServer和BlazorWebAssembly创建高影响力的用户体验,这个UI自建库提供了一套全面的原生BlazorUI组件(包括PivotGrid、调度程序、图表、数据编辑器和报表等)。DevExpress Blazor控件目前已经升级到v23.2版本了,此版本进一步增强了可访问......
  • C - Merge the balls
    C-Mergetheballshttps://atcoder.jp/contests/abc351/tasks/abc351_c 思路使用stack记录序列路径对栈顶两个元素尝试做缩减处理。 Codehttps://atcoder.jp/contests/abc351/submissions/52873456intn;stack<longlong>sq;intmain(){cin>>n;......
  • EDevourer风险分析报告及典型用户
    1.风险分析人:客户/用户反馈多样性:由于用户群体可能包括不同年龄段、英语水平和游戏喜好的人群,因此他们的反馈可能多样化,需要花费更多精力去理解和整合这些反馈。团队成员经验和技能:团队成员在游戏开发的时侯有可能会遇到许多bug或者遇到设想的功能无法实现,可能导致开发过程中......
  • 使用 .NET 的 Dev Proxy 构建和测试弹性应用
    使用.NET的DevProxy构建和测试弹性应用https://devblogs.microsoft.com/dotnet/build-test-resilient-apps-dotnet-dev-proxy/在构建连接到API的应用时,我们通常专注于让应用正常工作。但是,当API速度慢、返回错误或不可用时会发生什么?你最不想看到的就是当你的应用程序......