Hyperledger Fabric 2.0 官方文档中文版 第5章 开发应用程序
- 总目录
- 5.开发应用程序
- 情景
- PaperNet网络
- 介绍参与者
- 分析
- 商业票据生命周期
- 交易
- 账本
- 过程和数据设计
- 生命周期
- 账本状态
- 状态键
- 多状态
- 信任关系
- 智能合约流程
- 智能合约
- 实现语言
- 合约类别
- 交易定义
- 交易逻辑
- 表示一个对象
- 访问账本
- 应用
- 基本流程
- 钱包
- 网关
- 网络通道
- 构造请求
- 提交交易记录
- 进程响应
- 应用设计元素
- 合约名称
- 链码命名空间
- 交易环境
- 交易处理程序
- 背书策略
- 连接配置文件
总目录
第1章 引言第2章 Hyperledger Fabric v2.0的新增功能第3章 关键概念第4章 入门第5章 开发应用程序第6章 教程(上)第6章 教程(下)第7章 部署生产网络第8章 操作指南第9章 升级到最新版本
5.开发应用程序
本主题介绍如何使用Hyperledger Fabric开发客户端应用程序和智能合约来解决业务问题。在一个涉及多个组织的真实商业文件场景中,您将了解实现此目标所需的所有概念和任务。我们假设区块链网络已经可用。
本主题面向多个受众:
- 解决方案和应用程序架构师
- 客户端应用程序开发人员
- 智能合约开发者
- 商务专业人士
您可以选择按顺序阅读主题,也可以根据需要选择各个部分。每个主题部分都会根据读者的相关性进行标记,所以无论您是在寻找业务信息还是技术信息,当某个主题适合您时,都会一目了然。
本主题遵循典型的软件开发生命周期。它从业务需求开始,然后涵盖开发应用程序和智能合约以满足这些需求所需的所有主要技术活动。
如果您愿意,您可以通过运行商业论文教程,按照简短的解释,立即尝试商业票据教程。当您需要对本教程中介绍的概念进行更全面的解释时,您可以返回本主题。
情景
受众:架构师、应用程序和智能合约开发人员、商业专业人士
在本主题中,我们将描述一个涉及六个组织的业务场景,这些组织使用PaperNet(一个基于Hyperledger Fabric构建的商业票据网络)来发行、购买和赎回商业票据。我们将使用该场景概述开发商业票据应用程序和参与者组织使用的智能合约的需求。
PaperNet网络
PaperNet是一个商业票据网络,允许适当授权的参与者发行、交易、赎回和评级商业票据。
PaperNet商业票据网络。目前有六家机构使用PaperNet网络发行、购买、出售、赎回和评级商业票据。MagentoCorp发行和赎回商业票据。DigiBank、BigFund、BrokerHouse和HedgeMatic之间的所有贸易商业票据。RateM为商业票据提供了多种风险度量方法。
让我们看看magneticorp是如何利用PaperNet和商业票据来帮助其业务的。
介绍参与者
magneticorp是一家备受尊敬的生产自动驾驶电动车的公司。在2020年4月初,magneticorp为Daintree赢得了一份生产10000辆D型汽车的订单,Daintree是个人运输市场的新进入者。尽管这一订单对magneticorp来说是一次重大的胜利,但Daintree在magneticorp和Daintree正式达成协议6个月后,在11月1日开始交付车辆之前,Daintree将不必支付费用。
为了制造这些汽车,magneticorp需要雇佣1000名工人,工作时间至少为6个月。这给它的财务状况带来了短期压力,每月需要额外500万美元来支付这些新员工的工资。商业票据旨在帮助magneticorp克服短期融资需求,以满足每个月的工资需求,因为Daintree预计当Daintree开始为其新的D型汽车买单时,它将拥有大量现金。
5月底,magneticorp需要500万美元来支付5月1日额外雇佣的工人的工资。为此,该公司发行了一份面值为500万美元的商业票据,到期日为未来6个月,届时该公司预计将从Daintree获得现金流。DigiBank认为magneticorp是有信誉的,因此在央行基准利率2%的基础上不需要太多溢价,6个月后,2%的基准利率将达到495万美元,即500万美元。因此,它以494万美元的价格购买了magneticorp的6个月期商业票据,与它的495万美元相比,这是一个小小的折扣。DigiBank完全预计,它将能够在6个月内从MagnetorCorp赎回500万美元,使其能够因承担与此商业票据相关的风险而获利1万美元。这额外的1万美元意味着它获得了2.4%的投资回报率——明显好于2%的无风险回报率。
6月底,当magneticorp以500万美元的价格发行了一份新的商业票据来支付6月份的工资时,BigFund以494万美元的价格收购了它。这是因为6月份的商业状况与5月份大致相同,导致BigFund对magneticorp商业票据的估值与DigiBank在5月份的估值相同。
接下来的每个月,MagnetorCorp可以发行新的商业票据来履行其工资义务,这些票据可以由DigiBank或PaperNet商业票据网络的任何其他参与者(BigFund、HedgeMatic或BrokerHouse)购买。这些机构可能会或多或少地支付商业票据的费用,这取决于两个因素——央行基准利率和与magneticorp相关的风险。后一个数字取决于多种因素,如D型车的产量,以及评级机构RateM评估的MagnetorCorp的信誉度。
PaperNet中的组织有不同的角色,MagnetorCorp发行票据、DigiBank、BigFund、HedgeMatic和BrokerHouse交易票据和利率票据。同样角色的组织,如DigiBank、Bigfund、HedgeMatic和BrokerHouse都是竞争对手。不同角色的组织不一定是竞争对手,但也可能有相反的商业利益,例如MagentoCorp希望其报纸获得高评级以高价出售,而DigiBank则会从低评级中获益,因此可以低价买入。可以看出,即使像PaperNet这样看似简单的网络也可以有复杂的信任关系。区块链有助于在竞争对手或有可能导致纠纷的对立商业利益的组织之间建立信任。Fabric尤其具有捕获细粒度信任关系的方法。
让我们暂停一下magneticorp的故事,开发PaperNet用来发行、购买、出售和赎回商业票据的客户端应用程序和智能合约,以及捕获组织之间的信任关系。我们稍后再讨论评级机构RateM的角色。
分析
受众:架构师、应用程序和智能合约开发人员、商业专业人士
让我们更详细地分析一下商业票据。PaperNet的参与者,如magneticorp和DigiBank使用商业票据交易来实现他们的商业目标——让我们来研究一下商业票据的结构以及随着时间的推移影响它的交易。我们还将考虑PaperNet中的哪些组织需要基于网络中组织之间的信任关系来签署交易。稍后,我们将重点讨论资金如何在买方和卖方之间流动;现在,让我们关注由MagnetorCorp发行的第一份报告。
商业票据生命周期
5月31日,magneticorp发行了一份00001号票据。花点时间看看本文的第一个状态,以及它的不同属性和值:
Issuer = MagnetoCorp
Paper = 00001
Owner = MagnetoCorp
Issue date = 31 May 2020
Maturity = 30 November 2020
Face value = 5M USD
Current state = issued
这张纸的状态是发行交易的结果,它带来了磁公司的第一张商业票据!请注意,这张报纸的面值为500万美元,可在今年晚些时候赎回。看看发行00001号票据时发行人Issuer
和所有者Owner
是如何相同的。请注意,这张纸可以被唯一地标识为magneticorp0001
——由发行人Issuer
和纸张Paper
属性组成。最后,看看属性Current state=issued
如何快速识别magneticorp paper 00001在其生命周期中的阶段。
发行后不久,DigiBank收购了该报。花点时间看看同一份商业票据是如何因这次买入交易而发生变化的:
Issuer = MagnetoCorp
Paper = 00001
Owner = DigiBank
Issue date = 31 May 2020
Maturity date = 30 November 2020
Face value = 5M USD
Current state = trading
最重要的变化是所有者Owner
的变化——看看最初由Magneticorp
拥有的报纸现在是由DigiBank
拥有的。我们可以想象,该票据随后将如何出售给BrokerHouse或HedgeMatic,以及相应的所有者
变更。请注意,目前状态Current state
如何让我们很容易地识别出该报目前正在交易
。
6个月后,如果DigiBank仍然持有商业票据,它可以向MagnetorCorp赎回:
Issuer = MagnetoCorp
Paper = 00001
Owner = MagnetoCorp
Issue date = 31 May 2020
Maturity date = 30 November 2020
Face value = 5M USD
Current state = redeemed
最后的赎回交易结束了商业票据的生命周期,可以认为它已经结束。通常必须对赎回的商业票据进行记录,赎回redeemed
状态允许我们快速识别这些票据。通过将所有者与交易创建者的身份进行比较,可以使用纸张所有者
的值对赎回交易执行访问控制。Fabric通过getCreator()
链码API支持这一点。如果golang用作链码语言,则可以使用客户端身份链码库来检索交易创建者的其他属性。
交易
我们已经看到,paper 00001的生命周期相对简单——它在发行
、买入
和赎回
交易之间移动。
这三笔交易由magneticorp和DigiBank(两次)发起,并推动了paper 00001的状态变化。让我们更详细地了解一下影响本文的交易:
发行
检查由magneticorp发起的第一笔交易:
Txn = issue
Issuer = MagnetoCorp
Paper = 00001
Issue time = 31 May 2020 09:00:00 EST
Maturity date = 30 November 2020
Face value = 5M USD
查看发行交易如何具有属性和值的结构。该交易结构与paper 00001的结构不同,但非常匹配。这是因为它们是不同的东西-paper 00001反映了PaperNet的状态,这是发行交易的结果。正是发行交易(我们看不到)背后的逻辑获取了这些属性并创建了票据。因为交易产生了票据,这意味着这些结构之间有着非常密切的关系。
参与发行交易的唯一机构是magneticorp。当然,magneticorp需要签署这笔交易。一般来说,票据发行人需要在发行新票据的交易上签字。
买入
接下来,研究一下将票据00001的所有权从MagnetoCorp转让给DigiBank的买入交易:
Txn = buy
Issuer = MagnetoCorp
Paper = 00001
Current owner = MagnetoCorp
New owner = DigiBank
Purchase time = 31 May 2020 10:00:00 EST
Price = 4.94M USD
看看在本文中,买入交易的属性是如何减少的。那是因为这次交易只修改了这张票据。只有New owner=DigiBank
会因为这个交易而改变;其他一切都是一样的。没关系,买入交易最重要的是所有权的变更,事实上,在这桩交易中,我们承认了该报目前的所有者,magneticorp。
您可能会问,为什么在00001号票据中没有记录购买时间
和价格
属性?这又回到了交易和票据之间的区别。494万美元的标价实际上是交易的财产,而不是票据的财产。花点时间思考一下这种差异,它并不像看上去那么明显。稍后我们将看到,账本将记录这两种信息——影响票据的所有交易的历史记录,以及其最新状态。弄清楚这种信息的分离是非常重要的。
还值得记住的是,票据00001可能会多次买卖。尽管我们在我们的场景中跳过了一点,但让我们来看看如果paper 00001更改了所有权,我们可能会看到哪些交易。
如果我们有BigFund购买:
Issuer = MagnetoCorp
Paper = 00001
Current owner = DigiBank
New owner = BigFund
Purchase time = 2 June 2020 12:20:00 EST
Price = 4.93M USD
随后由HedgeMatic进行后续买入:
Txn = buy
Issuer = MagnetoCorp
Paper = 00001
Current owner = BigFund
New owner = HedgeMatic
Purchase time = 3 June 2020 15:59:00 EST
Price = 4.90M USD
看看票据所有者是如何变化的,以及在我们的示例中,价格是如何变化的。你能想出MagnetoCorp商业票据价格下跌的原因吗?
直观地说,买入交易要求销售和购买组织都需要签署这样的交易,以证明双方之间的相互同意是交易的一部分。
赎回
票据00001的赎回交易代表其生命周期的结束。在我们相对简单的例子中,HedgeMatic启动了将商业票据转让回MagnetoCorp的交易:
Txn = redeem
Issuer = MagnetoCorp
Paper = 00001
Current owner = HedgeMatic
Redeem time = 30 Nov 2020 12:00:00 EST
同样,请注意赎回交易的属性非常少;对票据00001的所有更改都可以通过赎回交易逻辑计算数据:发行人
将成为新的所有者,当前状态
将更改为已赎回
。在我们的示例中指定了Current owner
属性,因此可以根据当前的纸张持有者对其进行检查。
从信托的角度来看,买入交易的相同推理也适用于赎回指令:参与交易的两个组织都必须签署该指令。
账本
在本主题中,我们了解了交易和生成的票据状态是PaperNet中最重要的两个概念。事实上,我们将在任何一个Hyperledger Fabric的分布式账本中看到这两个基本元素:一个世界状态,包含所有对象的当前值;一个区块链,记录导致当前世界状态的所有交易的历史。
交易记录所需的签核是通过规则强制执行的,这些规则在将交易附加到账本之前进行评估。只有当所需的签名存在时,Fabric才会接受有效的交易。
你现在在一个很好的地方把这些想法转化成一个智能合约。如果你的程序有点生疏,不用担心,我们会提供一些提示和提示来理解程序代码。掌握商业票据智能合约是设计自己的应用程序的第一大步。或者,如果你是一个对编程很熟悉的业务分析师,不要害怕继续深入挖掘!
过程和数据设计
受众:架构师、应用程序和智能合约开发人员、商业专业人士
本主题介绍如何在PaperNet中设计商业票据流程及其相关的数据结构。我们的分析强调了使用状态和事务对PaperNet进行建模提供了一种精确的方法来理解正在发生的事情。现在我们将详细介绍这两个紧密相关的概念,以帮助我们随后设计PaperNet的智能合约和应用程序。
生命周期
正如我们所看到的,在处理商业票据时,有两个重要的概念与我们有关:状态和交易。事实上,所有区块链用例都是如此;有一些概念性的价值对象,被建模为状态,其生命周期转换由交易来描述。对状态和交易进行有效分析是成功实施的重要起点。
我们可以使用状态转换图来表示商业票据的生命周期:
商业票据的状态转移图。商业票据通过发行、买入和赎回交易在发行、交易和赎回状态之间进行转换。
请参阅状态图如何描述商业票据如何随时间变化,以及特定事务如何控制生命周期转换。在Hyperledger Fabric中,智能合约实现了在不同状态之间转换商业票据的交易逻辑。商业票据状态实际上是在账本世界状态下持有的;所以让我们仔细看看它们。
账本状态
回想一下商业票据的结构:
商业票据可以表示为一组属性,每个属性都有一个值。通常,这些属性的某些组合将为每张纸张提供一个唯一的密钥。
看看商业票据
资产的价值是00001
,而面值资产
的价值是500万美元
。最重要的是,当前状态
属性表明商业票据是发行
、买入
还是赎回
。综合起来,全套属性构成了商业票据的状态。此外,这些单个商业票据状态的全部集合构成了账本世界状态。`
所有账本状态都共享此窗体;每个状态都有一组属性,每个属性具有不同的值。状态的多属性方面是一个强大的特性,它允许我们将Fabric状态看作一个向量,而不是一个简单的标量。然后,我们将有关整个对象的事实表示为单个状态,这些状态随后经历由交易逻辑控制的转换。Fabric状态被实现为键/值对,其中值以捕获对象的多个属性(通常是JSON)的格式对对象属性进行编码。账本数据库可以支持针对这些属性的高级查询操作,这对于复杂的对象检索非常有用。
看看MagnetorCorp的票据00001
如何表示为一个状态向量,根据不同的交易刺激进行转换:
商业票据状态是由不同的交易产生的,并随着不同的交易而转变。Hyperledger Fabric状态具有多个属性,使它们成为向量而不是标量。
注意每个单独的纸张是如何从空状态开始的,从技术上讲,这是一个空(nil)
状态,因为它不存在!请参阅票据00001
是如何通过发行交易产生的,以及它是如何随着买入和赎回交易而更新的。
注意每个状态是如何自我描述的;每个属性都有一个名称和一个值。虽然我们所有的商业票据目前都有相同的属性,但这不一定是永远的情况,因为Hyperledger Fabric支持具有不同属性的不同状态。这允许同一账本世界状态包含相同资产的不同形式以及不同类型的资产。它还可以更新一个状态的结构;设想一个新的法规需要一个额外的数据字段。灵活的状态属性支持数据随时间演化的基本要求。
状态键
在大多数实际应用程序中,一个状态将具有在给定上下文中唯一标识它的属性的组合-它是键。PaperNet商业票据的关键是由发行人
和票据
属性串联而成;因此,对于MagnetoCorp的第一篇论文,它是MagnetoCorp0001
。
状态键允许我们唯一地标识一张票据;它是作为发行交易的结果创建的,随后由“买入”和“赎回”进行更新。Hyperledger Fabric要求账本中的每个状态都有一个唯一的密钥。
当从可用属性集中找不到唯一键时,应用程序确定的唯一键将指定为创建状态的交易的输入。这个唯一的键通常带有某种形式的UUID,虽然可读性较差,但这是一种标准做法。重要的是账本中每个单独的状态对象都必须有一个唯一的键。
注意:您应该避免在键中使用U+0000(nil字节)。
多状态
如我们所见,PaperNet中的商业票据作为状态向量存储在账本中。这是一个合理的要求,能够从账本查询不同的商业票据;例如:查找所有由magneticorp发行的票据,或者:查找magneticorp在赎回
状态下发行的所有票据。
为了使这些类型的搜索任务成为可能,将所有相关的论文组合在一个逻辑列表中是很有帮助的。PaperNet的设计融入了商业票据清单的理念——一个逻辑容器,每当商业票据发行或变更时,它都会更新。
逻辑表示
将所有PaperNet商业票据都放在一个商业票据列表中是很有帮助的:
MagnetorCorp新创建的商业票据00004被添加到现有商业票据的列表中。
新的票据可以作为发行交易的结果添加到列表中,并且已经在列表中的票据可以通过买入或赎回交易进行更新。查看列表的描述性名称:org.papernet.papers
;使用这种DNS名称是一个非常好的主意,因为选择好的名称将使您的区块链设计对其他人来说更直观。这个想法同样适用于智能合约名称。
物理表示
虽然在PaperNet上只列出一个论文列表是正确的-org.papernet.papers
–列表最好实现为一组单独的Fabric状态,其组合键将状态与其列表相关联。这样,每个状态的组合键都是唯一的,并且支持有效的列表查询。
将PaperNet商业票据的列表表示为一组不同的Hyperledger Fabric状态
注意列表中的每一篇文章是如何用一个向量状态来表示的,它是由org.papernet.paper
,发行人
和票据
属性。这种结构有两个原因:
- 它允许我们检查账本中的任何状态向量,以确定它在哪个列表中,而无需参考单独的列表。这类似于观察一组体育迷,并根据他们所穿球衣的颜色来确定他们支持哪支球队。体育迷们自己宣布他们的忠诚,我们不需要球迷的名单。
- Hyperlegder Fabric在内部使用一种并发控制机制来更新账本,这样就可以将文件保存在不同的状态向量中,从而大大减少共享状态冲突的机会。这种冲突需要重新提交交易,使应用程序设计复杂化,并降低性能。
这实际上是一个非常重要的物理性能的关键。把你们的状态分开!
信任关系
我们已经讨论了网络中的不同角色,如发行人、交易员或评级机构以及不同的商业利益如何决定谁需要签署交易。在Fabric中,这些规则由所谓的背书策略捕获。可以在链码粒度上设置规则,也可以为单个状态键设置规则。
这意味着在PaperNet中,我们可以为整个命名空间设置一个规则,以确定哪些组织可以发布新的论文。稍后,可以为单个文件设置和更新规则,以捕获购买和赎回交易的信任关系。
在下一个主题中,我们将向您展示如何结合这些设计概念来实现PaperNet商业票据智能合约,并在中应用它!
智能合约流程
受众:架构师、应用程序和智能合约开发人员
区块链网络的核心是智能合约。在PaperNet中,商业票据智能合约中的代码定义了商业票据的有效状态,以及将票据从一种状态转换到另一种状态的交易逻辑。在本主题中,我们将向您展示如何实现一个现实世界中控制商业票据发行、买入和赎回过程的智能合约。
我们将介绍:
如果您愿意,您可以下载示例,甚至可以在本地运行它。它是用JavaScript和Java编写的,但是它的逻辑与语言无关,所以您可以很容易地看到发生了什么!(样品也将可用于Go。)
智能合约
智能合约定义业务对象的不同状态,并控制在这些不同状态之间移动对象的流程。智能合约之所以重要,是因为它们允许架构师和智能合约开发者定义在区块链网络中协作的不同组织之间共享的关键业务流程和数据。
在PaperNet网络中,智能合约由不同的网络参与者共享,如Magnetorp和DigiBank。所有连接到网络的应用程序必须使用同一版本的智能合约,以便它们共同实现相同的共享业务流程和数据。
实现语言
支持两个运行时,Java虚拟机和Node.js. 这使我们有机会使用JavaScript、TypeScript、Java或任何其他可以在这些支持的运行时中运行的语言之一。
在Java和TypeScript中,注释或修饰符用于提供有关智能合约及其结构的信息。这样可以获得更丰富的开发体验—例如,可以强制执行作者信息或返回类型。在JavaScript中,必须遵循约定,因此,对于可以自动确定的内容有一些限制。
这里用JavaScript和Java两种语言给出了示例。
合约类别
PaperNet商业票据智能合约的副本包含在单个文件中。使用浏览器查看,或者在您最喜欢的编辑器中打开它(如果您已下载)。
-
papercontract.js
-JavaScript版本 -
CommercialPaperContract.java
-Java版本
您可能会从文件路径中注意到这是magneticorp的智能合约副本。magneticorp和DigiBank必须就他们将要使用的智能合约的版本达成一致。现在,你使用哪个组织的拷贝并不重要,它们都是一样的。
花点时间看看智能合约的整体结构;注意它很短!在文件的顶部,您将看到商业票据智能合约的定义:
JavaScript:
class CommercialPaperContract extends Contract {...}
Java:
@Contract(...)
@Default
public class CommercialPaperContract implements ContractInterface {...}
CommercialPaperContract
类包含商业票据的交易定义–发行、买入和赎回。正是这些交易使商业票据得以存在,并使其贯穿其生命周期。我们很快将研究这些交易,但现在请注意JavaScript,CommericalPaperContract
扩展了Hyperledger Fabric Contract
类。
对于Java,类必须用@Contract(…)
注释修饰。这提供了提供有关合同的其他信息的机会,例如许可证和作者。@Default()
注释指示此协定类是默认协定类。能够将一个合约类标记为默认合约类在一些具有多个合约类的智能合约中非常有用。
如果使用的是TypeScript实现,则有类似的@Contract(…)
注释,它们实现了与Java中相同的目的。
有关可用注释的详细信息,请参阅可用的API文档:
这些类、注释和Context
类在前面已被纳入范围:
JavaScript:
const { Contract, Context } = require('fabric-contract-api');
Java:
import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ContractInterface;
import org.hyperledger.fabric.contract.annotation.Contact;
import org.hyperledger.fabric.contract.annotation.Contract;
import org.hyperledger.fabric.contract.annotation.Default;
import org.hyperledger.fabric.contract.annotation.Info;
import org.hyperledger.fabric.contract.annotation.License;
import org.hyperledger.fabric.contract.annotation.Transaction;
我们的商业票据合同将使用这些类的内置特性,例如自动方法调用、每个交易上下文、交易处理程序和类共享状态。
还请注意JavaScript类构造函数如何使用其超类以显式合约名称初始化自身:
constructor() {
super('org.papernet.commercialpaper');
}
对于Java类,构造函数是空的,因为可以在@contract()
注释中指定显式的协定名称。如果不存在,则使用类的名称。
最重要的是,org.papernet.commercialpaper
非常具有描述性–此智能合约是所有PaperNet组织对商业票据的一致定义。
通常每个文件只有一个智能合约-合约往往有不同的生命周期,这使得将它们分开是明智之举。然而,在某些情况下,多个智能合约可能会为应用程序提供语法帮助,例如EuroBond
、Dollabond
、YenBond
,但本质上提供相同的功能。在这种情况下,智能合约和交易可以消除歧义。
交易定义
在类中,找到issue
方法。
JavaScript:
async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {...}
Java:
@Transaction
public CommercialPaper issue(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String issueDateTime,
String maturityDateTime,
int faceValue) {...}
Java注释@Transaction
用于将此方法标记为交易定义;TypeScript有一个等效的注释。
每当调用此合同以发行
商业票据时,此函数就被赋予控制权。回想一下商业票据00001是如何通过以下交易创建的:
Txn = issue
Issuer = MagnetoCorp
Paper = 00001
Issue time = 31 May 2020 09:00:00 EST
Maturity date = 30 November 2020
Face value = 5M USD
我们已经为编程风格更改了变量名,但是看看这些属性如何几乎直接映射到issue
方法变量。
每当申请者提出发行
商业票据的请求时,合同会自动授予发行方法的控制权。交易属性值通过相应的变量提供给方法。在应用程序主题中,使用示例应用程序程序,查看应用程序如何使用Hyperledger Fabric SDK提交交易。
您可能已经注意到问题定义中有一个额外的变量—ctx
。它被称为交易环境,它总是第一个。默认情况下,它维护与交易逻辑相关的每个合约和每个交易的信息。例如,它将包含magneticorp指定的交易标识符、magneticorp颁发用户数字证书以及对ledger API的访问。
了解智能合约如何通过实现自己的createContext()
方法而不是接受默认实现来扩展默认交易环境:
JavaScript:
createContext() {
return new CommercialPaperContext()
}
Java:
@Override
public Context createContext(ChaincodeStub stub) {
return new CommercialPaperContext(stub);
}
此扩展环境将自定义特性paperList添加到默认值:
JavaScript:
class CommercialPaperContext extends Context {
constructor() {
super();
// All papers are held in a list of papers
this.paperList = new PaperList(this);
}
Java:
class CommercialPaperContext extends Context {
public CommercialPaperContext(ChaincodeStub stub) {
super(stub);
this.paperList = new PaperList(this);
}
public PaperList paperList;
}
我们将很快看到如何使用ctx.paperList
来帮助存储和检索所有PaperNet商业票据。
要巩固您对智能合约交易结构的理解,请找到“买入”和“赎回”交易定义,并查看它们如何映射到相应的商业票据交易。
买入交易:
Txn = buy
Issuer = MagnetoCorp
Paper = 00001
Current owner = MagnetoCorp
New owner = DigiBank
Purchase time = 31 May 2020 10:00:00 EST
Price = 4.94M USD
JavaScript:
async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseTime) {...}
Java:
@Transaction
public CommercialPaper buy(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String currentOwner,
String newOwner,
int price,
String purchaseDateTime) {...}
赎回交易:
Txn = redeem
Issuer = MagnetoCorp
Paper = 00001
Redeemer = DigiBank
Redeem time = 31 Dec 2020 12:00:00 EST
JavaScript:
async redeem(ctx, issuer, paperNumber, redeemingOwner, redeemDateTime) {...}
Java:
@Transaction
public CommercialPaper redeem(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String redeemingOwner,
String redeemDateTime) {...}
在这两种情况下,观察商业票据交易与智能合约方法定义之间的1:1对应关系。
所有的JavaScript函数都使用async
和await
关键字,这些关键字允许JavaScript函数被视为同步函数调用。
交易逻辑
既然您已经了解了契约的结构和事务是如何定义的,那么让我们关注一下智能合约中的逻辑。
召回第一次发行交易:
Txn = issue
Issuer = MagnetoCorp
Paper = 00001
Issue time = 31 May 2020 09:00:00 EST
Maturity date = 30 November 2020
Face value = 5M USD
这会导致发行方法被传递控制:
JavaScript:
async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {
// create an instance of the paper
let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue);
// Smart contract, rather than paper, moves paper into ISSUED state
paper.setIssued();
// Newly issued paper is owned by the issuer
paper.setOwner(issuer);
// Add the paper to the list of all similar commercial papers in the ledger world state
await ctx.paperList.addPaper(paper);
// Must return a serialized paper to caller of smart contract
return paper.toBuffer();
}
Java:
@Transaction
public CommercialPaper issue(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String issueDateTime,
String maturityDateTime,
int faceValue) {
System.out.println(ctx);
// create an instance of the paper
CommercialPaper paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime,
faceValue,issuer,"");
// Smart contract, rather than paper, moves paper into ISSUED state
paper.setIssued();
// Newly issued paper is owned by the issuer
paper.setOwner(issuer);
System.out.println(paper);
// Add the paper to the list of all similar commercial papers in the ledger
// world state
ctx.paperList.addPaper(paper);
// Must return a serialized paper to caller of smart contract
return paper;
}
逻辑很简单:取事务输入变量,创建一个新的商业票据paper
,使用paperList
将其添加到所有商业票据的列表中,并返回新的商业票据(序列化为缓冲区)作为交易响应。
请参阅如何从交易环境检索paperList
以提供对商业票据列表的访问。issue()
、buy()
和reduce()
不断重新访问ctx.paperList
使商业报纸的清单保持最新。
买入交易的逻辑更为复杂:
JavaScript:
async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseDateTime) {
// Retrieve the current paper using key fields provided
let paperKey = CommercialPaper.makeKey([issuer, paperNumber]);
let paper = await ctx.paperList.getPaper(paperKey);
// Validate current owner
if (paper.getOwner() !== currentOwner) {
throw new Error('Paper ' + issuer + paperNumber + ' is not owned by ' + currentOwner);
}
// First buy moves state from ISSUED to TRADING
if (paper.isIssued()) {
paper.setTrading();
}
// Check paper is not already REDEEMED
if (paper.isTrading()) {
paper.setOwner(newOwner);
} else {
throw new Error('Paper ' + issuer + paperNumber + ' is not trading. Current state = ' +paper.getCurrentState());
}
// Update the paper
await ctx.paperList.updatePaper(paper);
return paper.toBuffer();
}
Java:
@Transaction
public CommercialPaper buy(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String currentOwner,
String newOwner,
int price,
String purchaseDateTime) {
// Retrieve the current paper using key fields provided
String paperKey = State.makeKey(new String[] { paperNumber });
CommercialPaper paper = ctx.paperList.getPaper(paperKey);
// Validate current owner
if (!paper.getOwner().equals(currentOwner)) {
throw new RuntimeException("Paper " + issuer + paperNumber + " is not owned by " + currentOwner);
}
// First buy moves state from ISSUED to TRADING
if (paper.isIssued()) {
paper.setTrading();
}
// Check paper is not already REDEEMED
if (paper.isTrading()) {
paper.setOwner(newOwner);
} else {
throw new RuntimeException(
"Paper " + issuer + paperNumber + " is not trading. Current state = " + paper.getState());
}
// Update the paper
ctx.paperList.updatePaper(paper);
return paper;
}
在用paper.setOwner(newOwner)
更改所有者之前,请查看交易如何检查currentOwner
和票据
是TRADING
。但是基本流程很简单–检查一些先决条件,设置新所有者,更新账本上的商业票据,并将更新后的商业票据(序列化为缓冲区)作为交易响应返回。
你为什么不看看你能不能理解赎回交易的逻辑?
表示一个对象
我们已经了解了如何使用CommercialPaper
和PaperList
类定义和实现发行、买入和赎回交易。让我们看看这些类是如何工作的来结束这个主题的。
找到CommercialPaper
类:
JavaScript:
在paper.js文件中:
class CommercialPaper extends State {...}
Java:
在CommercialPaper.java文件中:
@DataType()
public class CommercialPaper extends State {...}
此类包含商业票据状态的内存表示形式。请参见createInstance
方法如何使用提供的参数初始化新的商业票据:
JavaScript:
static createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {
return new CommercialPaper({ issuer, paperNumber, issueDateTime, maturityDateTime, faceValue });
}
Java:
public static CommercialPaper createInstance(String issuer, String paperNumber, String issueDateTime,
String maturityDateTime, int faceValue, String owner, String state) {
return new CommercialPaper().setIssuer(issuer).setPaperNumber(paperNumber).setMaturityDateTime(maturityDateTime)
.setFaceValue(faceValue).setKey().setIssueDateTime(issueDateTime).setOwner(owner).setState(state);
}
回想一下这个类是如何被issue
交易使用的:
JavaScript:
let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue);
Java:
CommercialPaper paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime,
faceValue,issuer,"");
看看每次调用issue交易时,都会创建一个包含交易数据的新的商业票据内存实例。
需要注意的几个要点:
- 这是一个内存中的表示;稍后我们将看到它在账本上的显示方式。
-
CommercialPaper
类继承了State
类。State
是一个应用程序定义的类,它为一个状态创建一个公共抽象。所有状态都有它们表示的业务对象类、复合键、可以序列化和反序列化等。当我们在账本上存储多个业务对象类型时,State
帮助我们的代码更加易读。在中检查State类state.js
文件。 - 一张单据在创建时会计算它自己的键-当访问账本时将使用该键。密钥由
issuer
和paperNumber
组合而成。
constructor(obj) {
super(CommercialPaper.getClass(), [obj.issuer, obj.paperNumber]);
Object.assign(this, obj);
}
- 通过交易而不是通过paper类将单据移动到
ISSUED
状态。这是因为智能合约控制着单据的生命周期状态。例如,import
交易可能会立即在TRADING
状态下创建一组新的票据。
CommercialPaper
类的其余部分包含简单的helper方法:
getOwner() {
return this.owner;
}
回想一下智能合约是如何使用这种方法来推动商业票据的生命周期的。例如,在赎回交易中,我们看到:
if (paper.getOwner() === redeemingOwner) {
paper.setOwner(paper.getIssuer());
paper.setRedeemed();
}
访问账本
现在在paperlist.js
文件中找到PaperList
类:
class PaperList extends StateList {
此实用程序类用于管理Hyperledger Fabric状态数据库中的所有PaperNet商业票据。纸质列表数据结构在体系结构主题中有更详细的描述。
与CommercialPaper
类类似,该类继承了一个应用程序定义的StateList
类,该类为状态列表创建了一个公共抽象——在本例中,是PaperNet中的所有商业票据。
addPaper()
方法是StateList.addState()
方法:
async addPaper(paper) {
return this.addState(paper);
}
你可以看到StateList.js
文件中StateList
类如何使用Fabric API putState()
将商业票据作为状态数据写入账本:
async addState(state) {
let key = this.ctx.stub.createCompositeKey(this.name, state.getSplitKey());
let data = State.serialize(state);
await this.ctx.stub.putState(key, data);
}
账本中的每一条状态数据都需要以下两个基本元素:
- 键:
key
由createCompositeKey()
使用固定名称和state
键组成。在构造PaperList
对象时指定了名称,并且state.getSplitKey()
确定每个状态的唯一密钥。 - 数据:
data
只是商业票据状态的序列化形式,使用State.serialize()
实用方法。State
类使用JSON序列化和反序列化数据,而State的业务对象类(在我们的示例中是CommercialPaper
是在PaperList
对象构建时设置的。
请注意StateList
如何不存储关于单个状态或状态总列表的任何信息—它将所有这些都委托给Fabric状态数据库。这是一个重要的设计模式——它减少了在Hyperledger Fabric中发生ledger MVCC冲突的机会。
StateList getState()
和updateState()
方法的工作方式类似:
async getState(key) {
let ledgerKey = this.ctx.stub.createCompositeKey(this.name, State.splitKey(key));
let data = await this.ctx.stub.getState(ledgerKey);
let state = State.deserialize(data, this.supportedClasses);
return state;
}
async updateState(state) {
let key = this.ctx.stub.createCompositeKey(this.name, state.getSplitKey());
let data = State.serialize(state);
await this.ctx.stub.putState(key, data);
}
了解他们如何使用Fabric APIs putState()
、getState()
和createCompositeKey()
访问账本。稍后我们将扩展这个智能合约,列出paperNet中的所有商业票据——实现这种账本检索的方法是什么?
就是这样!在本主题中,您了解了如何实现PaperNet的智能合约。您可以转到下一个子主题,了解应用程序如何使用Fabric SDK调用智能合约。
应用
受众:架构师、应用程序和智能合约开发人员
应用程序可以通过向账本提交交易或查询账本内容与区块链网络进行交互。本主题介绍应用程序如何执行此操作的机制;在我们的场景中,组织使用调用商业票据智能合约中定义的发行、买入和赎回交易的应用程序访问PaperNet。尽管magneticorp发行商业票据的申请是基本的,但它涵盖了所有的主要理解点。
在本主题中,我们将介绍:
为了帮助您理解,我们将参考Hyperledger Fabric提供的商业票据示例应用程序。你可以下载它并在本地运行。它是用JavaScript和Java编写的,但是它的逻辑是完全独立于语言的,所以您可以很容易地看到发生了什么!(示例也将可用于Go。)
基本流程
应用程序使用Fabric SDK与区块链网络交互。以下是应用程序如何调用商业票据智能合约的简化图表:
PaperNet应用程序调用商业票据智能合约来提交问题交易请求。
申请必须遵循六个基本步骤才能提交交易:
- 从电子钱包中选择一个身份
- 连接到网关
- 访问所需网络
- 为智能合约构造交易请求
- 向网络提交交易
- 处理响应
您将看到一个典型的应用程序如何使用Fabric sdk执行这六个步骤。您可以在issue.js
文件中找到应用代码。可在您的浏览器中查看,或者在您喜欢的编辑器中打开它(如果您已下载)。花点时间看看应用程序的整体结构;即使有注释和间距,也只有100行代码!
钱包
朝着issue.js
的顶部,您将看到两个Fabric类被纳入范围:
const { Wallets, Gateway } = require('fabric-network');
您可以在node-SDK文档中阅读fabric-network
类,但现在,让我们看看如何使用它们将magneticorp的应用程序连接到PaperNet。应用程序使用Fabric Wallet类,如下所示:
const wallet = await Wallets.newFileSystemWallet('../identity/user/isabella/wallet');
查看wallet
如何在本地文件系统中找到钱包。从钱包中检索到的身份显然是一个名为Isabella的用户,她正在使用issue
应用程序。钱包里有一组身份信息——X.509数字证书——可用于访问PaperNet或任何其他Fabric网络。如果您运行教程,并在这个目录中查找,您将看到Isabella的身份凭证。
想象一下一个钱包,里面装着你的身份证、驾驶执照或ATM卡的数字等价物。其中的X.509数字证书将把持有者与一个组织联系起来,从而使他们有权在网络通道中享有权利。例如,Isabella
可能是magneticorp的管理员,这可能会给她比另一个用户(DigiBank的Balaji
)更多的特权。此外,智能合约可以在使用交易环境的智能合约处理期间检索此身份。
另外请注意,钱包里没有任何形式的现金或代币——它们持有身份。
网关
第二个密钥类是Fabric网关。最重要的是,网关身份提供对网络的访问的一个或多个节点,在我们的例子中是PaperNet。看看如何issue.js
连接到其网关:
await gateway.connect(connectionProfile, connectionOptions);
gateway.connect()
有两个重要参数:
- connectionProfile:连接配置文件的文件系统位置,它将一组对等点标识为PaperNet的网关
- connectionOptions:一组用于控制
issue.js
与PaperNet互动
了解客户端应用程序如何使用网关将自身与网络拓扑隔离,网络拓扑可能会发生变化。网关负责使用连接配置文件和连接选项将交易提案发送到网络中的正确节点。
花点时间检查连接配置文件./gateway/connectionProfile.yaml
。它使用YAML,使其易于阅读。
它被加载并转换为JSON对象:
let connectionProfile = yaml.safeLoad(file.readFileSync('./gateway/connectionProfile.yaml', 'utf8'));
现在,我们只对profile中的channels:
和peers:
部分感兴趣:(我们稍微修改了细节,以便更好地解释发生了什么。)
channels:
papernet:
peers:
peer1.magnetocorp.com:
endorsingPeer: true
eventSource: true
peer2.digibank.com:
endorsingPeer: true
eventSource: true
peers:
peer1.magnetocorp.com:
url: grpcs://localhost:7051
grpcOptions:
ssl-target-name-override: peer1.magnetocorp.com
request-timeout: 120
tlsCACerts:
path: certificates/magnetocorp/magnetocorp.com-cert.pem
peer2.digibank.com:
url: grpcs://localhost:8051
grpcOptions:
ssl-target-name-override: peer1.digibank.com
tlsCACerts:
path: certificates/digibank/digibank.com-cert.pem
请参见channel:
如何识别PaperNet:
网络通道及其两个节点。MagnetoCorp有peer1.magenetocorp.com
和DigiBank有peer2.digibank.com
,两者都扮演着背书节点的角色。通过peers:
key链接到这些节点,其中包含如何连接到它们的详细信息,包括它们各自的网络地址。
连接配置文件包含很多信息—不仅仅是节点—还有网络通道、网络排序节点、组织和CAs,因此如果您不了解所有信息,请不要担心!
现在我们将注意力转向connectionOptions
对象:
let connectionOptions = {
identity: userName,
wallet: wallet,
discovery: { enabled:true, asLocalhost: true }
};
看看它是如何指定身份、userName
和钱包、钱包
来连接网关的。这些值在代码的前面被赋值。
应用程序还可以使用其他连接选项来指示SDK智能地代表它执行操作。例如:
let connectionOptions = {
identity: userName,
wallet: wallet,
eventHandlerOptions: {
commitTimeout: 100,
strategy: EventStrategies.MSPID_SCOPE_ANYFORTX
},
}
这里,commitTimeout
告诉SDK等待100秒,以了解交易是否已提交。strategy: EventStrategies.MSPID_SCOPE_ANYFORTX
指定SDK可以在单个MagnetorCorp节点确认交易后通知应用程序,与strategy: EventStrategies.NETWORK_SCOPE_ALLFORTX相反
,这需要来自magneticorp和DigiBank的所有节点确认交易。
如果您愿意,请阅读更多关于连接选项如何允许应用程序指定面向目标的行为,而不必担心它是如何实现的。
网络通道
网关connectionProfile.yaml
中定义的节点为issue.js
提供了对PaperNet的访问。因为这些节点可以连接到多个网络通道,所以网关实际上为应用程序提供了对多个网络通道的访问权限!
查看应用程序如何选择特定通道:
const network = await gateway.getNetwork('PaperNet');
从这一点开始,network
将提供对PaperNet的访问。此外,如果应用程序希望同时访问另一个网络BondNet
,则很容易:
const network2 = await gateway.getNetwork('BondNet');
现在我们的应用程序可以访问第二个网络BondNet
和PaperNet
!
我们在这里可以看到Hyperledger Fabric的一个强大功能—应用程序可以通过连接到多个网关节点(每个网关节点连接到多个网络通道)参与网络网络。根据gateway.connect()
中提供的钱包身份,应用程序在不同的通道拥有不同的权限。
构造请求
该申请现在已经准备好发行商业票据了。要做到这一点,它将使用CommercialPaperContract
,同样,访问这个智能合约非常简单:
const contract = await network.getContract('papercontract', 'org.papernet.commercialpaper');
请注意应用程序如何提供名称(papercontract
)和明确的合同名称:org.papernet.commercialpaper
! 我们可以看到合同名称是如何从papercontract.js
包含许多合同的链码文件。在PaperNet上,papercontract.js
已安装并部署到名为papercontract
的通道,如果您感兴趣,请阅读如何部署包含多个智能合约的链码。
如果我们的应用程序同时要求访问PaperNet或BondNet中的另一个合同,这将很容易:
const euroContract = await network.getContract('EuroCommercialPaperContract');
const bondContract = await network2.getContract('BondContract');
在这些示例中,请注意我们如何没有使用限定的合约名称–每个文件只有一个智能合约,getContract()
将使用它找到的第一个合约。
回想一下magneticorp发行第一份商业票据的交易:
Txn = issue
Issuer = MagnetoCorp
Paper = 00001
Issue time = 31 May 2020 09:00:00 EST
Maturity date = 30 November 2020
Face value = 5M USD
现在我们把这个交易提交给PaperNet!
提交交易记录
提交交易是对SDK的单个方法调用:
const issueResponse = await contract.submitTransaction('issue', 'MagnetoCorp', '00001', '2020-05-31', '2020-11-30', '5000000');
查看submitTransaction()
参数如何与交易请求的参数匹配。这些值将传递给智能合约中的issue()
方法,并用于创建新的商业票据。回忆其签名:
async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {...}
智能合约可能会在应用程序发出submitTransaction()
后不久获得控制权,但事实并非如此。在幕后,SDK使用connectionOptions
和connectionProfile
详细信息将交易提案发送到网络中的正确节点,在那里它可以获得所需的背书。但应用程序不需要担心这些问题——它只需发出submitTransaction
,而SDK会处理所有这些问题!
注意submitTransaction
API包含一个监听交易提交的进程。监听提交是必需的,因为没有它,您将不知道您的交易是否已成功地排序、验证和提交到账本。
现在我们将注意力转向应用程序如何处理响应!
进程响应
回想一下papercontract.js
发行交易如何返回商业票据响应:
return paper.toBuffer();
您会注意到一个小小的问题——新的票据
需要在返回到应用程序之前转换为缓冲区。请注意issue.js
如何使用类方法CommercialPaper.fromBuffer()
将响应缓冲区重新rehydrate为商业票据:
let paper = CommercialPaper.fromBuffer(issueResponse);
这使得票据能够以自然的方式在描述性完成信息中使用:
console.log(`${paper.issuer} commercial paper : ${paper.paperNumber} successfully issued for value ${paper.faceValue}`);
看看同一个票据
类在应用程序和智能合约中是如何使用的——如果你这样组织代码,它将真正有助于可读性和再使用。
与交易提案一样,应用程序似乎在智能合约完成后不久就获得了控制权,但事实并非如此。在幕后,SDK管理整个共识过程,并根据策略
连接选项通知应用程序。如果您对SDK隐藏的功能感兴趣,请阅读详细的交易流。
就这样!在本主题中,通过研究magneticorp的应用程序如何在PaperNet中发布新的商业票据,您已经了解了如何从示例应用程序调用智能合约。现在检查一下关键账本和智能合约数据结构是由它们背后的架构主题设计的。
应用设计元素
合约名称
受众:架构师、应用程序和智能合约开发人员、管理员
链码是用于将代码部署到Hyperledger Fabric区块链网络的通用容器。链码中定义了一个或多个相关智能合约。每一个链码中的智能合约都有像独一无二身份一样的名称。应用程序使用链码中的合约名称访问特定的智能合约。
在本主题中,我们将介绍:
链码
在“开发应用程序”主题中,我们可以看到Fabric SDK如何提供高级编程摘要,帮助应用程序和智能合约开发人员集中精力解决业务问题,而不是如何与Fabric网络交互的低级细节。
智能合约是高级编程抽象的一个例子,可以在链码容器中定义智能合约。当链码安装在节点上并部署到通道上时,其中的所有智能合约都可用于您的应用程序。
可以在链码内定义多个智能合约。在链码中,每个节点都通过其名称进行唯一标识。
在上图中,链码A中定义了三个智能合约,而链码B有四个智能合约。查看如何使用链码名称完全限定特定智能合约。
账本结构由一组已部署的智能合约定义。这是因为账本包含了与网络相关的业务对象(如PaperNet中的商业票据)相关的事实,这些业务对象通过智能合约中定义的交易功能在其生命周期中移动(例如发行、买入、赎回)。
在大多数情况下,链码中只定义了一个智能合约。然而,将相关的智能合约放在一个链码中是有意义的。例如,以不同货币计价的商业票据可能有EuroPaperContract
、DollarPaperContract
、YenPaperContract
,这些合同可能需要在部署它们的通道中彼此保持同步。
名称
链码中的每个智能合约都由其合约名称作为唯一标识。智能合约可以在构造类时显式分配此名称,也可以让协定类隐式指定默认名称。
检查papercontract.js
链码文件:
class CommercialPaperContract extends Contract {
constructor() {
// Unique name when multiple contracts per chaincode file
super('org.papernet.commercialpaper');
}
查看CommercialPaperContract
构造函数如何将合同名称指定为org.papernet.commercialpaper
。 结果是,在papercontract
链码中,这个智能合约现在与合约名称org.papernet.commercialpaper
相关联。
如果没有指定明确的约定名称,则会指定一个默认名称-类的名称。在我们的示例中,默认的合同名称是CommercialPaperContract
。
仔细选择你的名字。不仅仅是每个智能合约都必须有一个唯一的名称;一个精心选择的名称是很有启发性的。具体地说,建议使用显式DNS样式命名约定来帮助组织清晰和有意义的名称;org.papernet.commercialpaper
表示PaperNet网络已经定义了一个标准的商业票据智能合约。
合约名称还有助于消除给定链码中具有相同名称的不同智能合约交易函数的歧义。当智能合约密切相关时,就会出现这种情况;它们的交易名称往往相同。我们可以看到,在一个通道中,交易是通过其链码和智能合约名称的组合来定义的。
合同名称在链码文件中必须唯一。一些代码编辑器在部署之前会检测到同一类名的多个定义。不管怎样,如果显式或隐式指定了多个具有相同协定名称的类,chaincode将返回错误。
应用
在节点上安装链码并将其部署到通道后,应用程序就可以访问其中的智能合约:
const network = await gateway.getNetwork(`papernet`);
const contract = await network.getContract('papercontract', 'org.papernet.commercialpaper');
const issueResponse = await contract.submitTransaction('issue', 'MagnetoCorp', '00001', '2020-05-31', '2020-11-30', '5000000');
查看应用程序如何使用network.getContract()
方法。papercontract
链码名称org.papernet.commercialpaper
返回一个contract
引用,该引用可用于提交交易处理以使用contract.submitTransaction()
API。
默认合约
链码中定义的第一个智能合约称为默认智能合约。默认值很有帮助,因为链码中通常会定义一个智能合约;默认值允许应用程序直接访问这些交易,而无需指定合约名称。
默认智能合约是在链码中定义的第一个合约。
在此图中,CommercialPaperContract
是默认的智能合约。尽管我们有两个智能合约,但默认智能合约使我们前面的示例更易于编写:
const network = await gateway.getNetwork(`papernet`);
const contract = await network.getContract('papercontract');
const issueResponse = await contract.submitTransaction('issue', 'MagnetoCorp', '00001', '2020-05-31', '2020-11-30', '5000000');
这是因为papercontract
中的默认智能合约是CommercialPaperContract
,并且它有一个issue
交易。请注意,BondContract
中的issue
交易只能通过显式寻址来调用。同样,即使cancel
交易是惟一的,因为BondContract
不是默认的智能合约,它也必须被显式地处理。
在大多数情况下,链码只包含一个智能合约,因此谨慎命名链码可以减少开发人员将链码视为一个概念的需要。在上面的示例代码中,感觉papercontract
是一个智能合约。
总之,合约名称是在给定链码内标识单个智能合约的一种简单机制。合同名称使应用程序能够轻松地找到特定的智能合约并使用它来访问账本。
链码命名空间
受众:架构师、应用程序和智能合约开发人员、管理员
链码命名空间允许它将其世界状态与其他链码分开。具体而言,同一链码中的智能合约共享对同一世界状态的直接访问,而不同链码中的智能合约则无法直接访问彼此的世界状态。如果智能合约需要访问另一个链码世界状态,它可以通过执行链码到链码调用来实现这一点。最后,区块链可以包含与不同世界状态相关的交易。
在本主题中,我们将介绍:
动机
命名空间是一个常见的概念。虽然西雅图街和纽约公园街的名字是一样的,但我们知道它们的名字是一样的。城市形成了公园街的名称空间,同时提供了自由和清晰。
在计算机系统中也是一样。名称空间允许不同的用户对共享系统的不同部分进行编程和操作,而不会相互妨碍。许多编程语言都有名称空间,这样程序就可以自由地分配唯一的标识符,比如变量名,而不必担心其他程序也这样做。我们将看到,Hyperledger Fabric使用名称空间来帮助智能合约保持其账本世界状态与其他智能合约分离。
方案
让我们使用下面的图表检查账本世界状态如何组织有关业务对象的事实,这些业务对象对通道中的组织很重要。无论这些对象是商业票据、债券还是车辆登记,以及它们在其生命周期中的任何位置,它们都作为状态保存在账本的世界状态数据库中。智能合约通过与账本(世界状态和区块链)交互来管理这些业务对象,在大多数情况下,这将涉及它的查询或更新账本世界状态。
理解账本世界状态是根据访问它的智能合约的链码进行分区的,这一点至关重要,而这种分区或名称空间是架构师、管理员和程序员在设计时的一个重要考虑因素。
根据访问账本的链码,账本世界状态被分为不同的名称空间。在给定的通道内,同一链码下的智能合约共享同一世界状态,不同链码的智能合约不能直接访问对方的世界状态。同样,区块链可以包含与不同链码世界状态相关的交易。
在我们的示例中,我们可以看到在两个不同的链码中定义了四个智能合约,每个链码都在各自的链码容器中。euroPaper
和yenPaper
智能合约在papers
链码中定义。euroPaper
和yenPaper
智能合约的情况类似,它们在bonds
链码中有定义。这种设计有助于应用程序程序员了解他们使用的是以欧元或日元计价的商业票据还是债券,而且由于每种金融产品的规则实际上不会因货币的不同而发生变化,因此在同一个链码中管理它们的部署是有意义的。
该图还显示了这种部署选择的后果。数据库管理系统(DBMS)为papers
和bonds
链码以及其中包含的智能合约创建不同的世界状态数据库。世界状态A和世界状态B分别保存在不同的数据库中;数据相互隔离,使得单个世界状态查询(例如)无法访问两个世界状态。据说世界状态是根据其链码命名的。
看看world state A
是如何包含商业报纸paperListEuro
和paperListYen
的。PAP11
和PAP21
分别是euroPaper
和yenPaper
智能合约管理的每一份文件的实例。因为它们共享相同的链码名称空间,所以它们的键(PAPxyz
)在papers
链码的名称空间中必须是唯一的,有点像街道名称在城镇中是唯一的。请注意,在papers
链码中编写一个智能合约,对所有商业票据(无论是以欧元还是日元计价)进行合计计算,因为它们共享相同的名称空间。债券的情况也类似——它们被存放在world state B
,后者映射到一个单独的bonds
数据库,它们的密钥必须是唯一的。
同样重要的是,名称空间意味着euroPaper
和yenPaper
不能直接访问world state B
,europond
和yenBond
不能直接访问world state A
。这种隔离很有帮助,因为商业票据和债券是非常不同的金融工具;它们具有不同的属性,受不同的规则约束。这也意味着票据和债券可以有相同的密钥,因为它们位于不同的名称空间中。这很有帮助;它为命名提供了很大的自由度。利用这种自由来有意义地命名不同的业务对象。
最重要的是,我们可以看到,区块链与在特定渠道中运行的对等方相关联,它包含的交易既影响world state A
,也影响world state B
。这是因为区块链是节点中包含的最基本的数据结构。世界状态集始终可以从该区块链中重新创建,因为它们是区块链交易的累积结果。世界状态有助于简化智能合约并提高其效率,因为它们通常只需要一个国家的当前价值。通过名称空间保持世界状态的分离有助于智能合约将其逻辑与其他智能合约隔离开来,而不必担心对应于不同世界状态的交易。例如,bonds
合同不需要担心paper
交易,因为它看不到它们的最终世界状态。
同样值得注意的是,节点、链码容器和DBMS都是逻辑上不同的进程。节点及其所有链码容器始终处于物理上独立的操作系统进程中,但DBMS可以配置为嵌入或独立,具体取决于其类型。对于LevelDB,DBMS完全包含在peer中,但是对于CouchDB,它是一个单独的操作系统进程。
请记住,本例中的名称空间选择是业务需求的结果,即共享不同货币的商业票据,但将它们与债券分开。考虑一下名称空间结构将如何修改,以满足保持每个金融资产类别独立或共享所有商业票据和债券的业务需求?
通道
如果一个节点加入到多个通道,则为每个通道创建和管理一个新的区块链。此外,每次将链码部署到新通道时,都会为其创建一个新的世界状态数据库。这意味着通道还与世界状态的链码一起形成一种命名空间。
但是,相同的节点和链码容器进程可以同时加入多个通道——与区块链和世界状态数据库不同,这些过程不会随着加入的通道数量而增加。
例如,如果你把票据
和债券
的链码部署到一个新的通道,就会创建一个完全独立的区块链,并创建两个新的世界状态数据库。但是,节点容器和链码容器不会增加;每个容器只会连接到多个通道。
用法
让我们用我们的商业文件示例来演示应用程序如何使用带有名称空间的智能合约。值得注意的是,应用程序与节点通信,节点将请求路由到适当的链码容器,然后该容器访问DBMS。此路由由图中所示的节点核心组件完成。
下面是一个应用程序的代码,它同时使用商业票据和债券,以欧元和日元计价。该准则相当不言自明:
const euroPaper = network.getContract(papers, euroPaper);
paper1 = euroPaper.submit(issue, PAP11);
const yenPaper = network.getContract(papers, yenPaper);
paper2 = yenPaper.submit(redeem, PAP21);
const euroBond = network.getContract(bonds, euroBond);
bond1 = euroBond.submit(buy, BON31);
const yenBond = network.getContract(bonds, yenBond);
bond2 = yenBond.submit(sell, BON41);
查看如何应用:
- 使用指定
papers
链码的getContract()
API访问euroPaper
和yenPaper
合约。见交互点1a和2a。 - 使用指定
bonds
链码的getContract()
API访问euroBond
和yenBond
合同。参见交互点3a和4a。 - 使用
euroPaper
合同向网络提交商业票据PAP11
的issue
交易。参见交互点1a。这导致在world state A
中创建了以状态PAP11
为代表的商业票据;交互点1b。该操作在交互点1c处被捕获为区块链中的一个交易。 - 使用
yenPaper
合同向网络提交商业票据PAP21
的redeem
交易。参见交互点2a。这导致在world state A
中创建一份由状态PAP21
代表的商业票据;交互点2b。该操作在交互点2c处被捕获为区块链中的一个交易。 - 使用
euroBond
合同向网络提交债券BON31
的买入交易。参见交互点3a。这导致在world state B
中创建由状态BON31
表示的债券;交互点3b。该操作在交互点3c处被捕获为区块链中的一个交易。 - 使用
yenBond
合同向网络提交债券BON41
的sell
交易。参见交互点4a。这导致在world state B
中创建一个由状态BON41
表示的债券;交互点4b。此操作在交互点4c处作为区块链中的一个交易捕获。
了解智能合约如何与世界状态互动:
-
euroPaper
和yenPaper
合同可以直接访问world state A
,但不能直接访问world state B
。world state A
物理上保存在与papers
链码对应的数据库管理系统(DBMS)中的papers
数据库中。 -
euroBond
和yenBond
合同可以直接访问world state B
,但不能直接访问world state A
。World state B
实际保存在bonds
链码对应的数据库管理系统(DBMS)中的bonds
数据库中。
了解区块链如何捕获世界状态的交易:
- 交互1c和2c分别对应于交易创建和更新商业文件
PAP11
和PAP21
。它们都包含在world state A
。 - 交互作用3c和4c对应于更新债券
BON31
和BON41
的交易。它们都包含在world state B
中。 - 如果
world state A
或world state A
因任何原因被破坏,则可以通过重放区块链中的所有交易来重新创建它们。
跨链访问
正如我们在示例场景中看到的,euroPaper
和yenPaper
无法直接访问world state B
。这是因为我们设计了链码和智能合约,使得这些链码和世界状态彼此独立。然而,让我们想象一下euroPaper
需要访问world state B
。
为什么会这样?想象一下,当一份商业票据发行时,智能合约希望根据到期日相似的债券的当前收益率来定价。在这种情况下,euroPaper
合同必须能够查询world state B
的债券价格。请看下面的图表,看看我们如何构建这种相互作用。
链码和智能合约如何通过链码间接访问另一个世界状态。
- 应用程序在
euroPaper
智能合约中提交一个issue
交易来发布PAP11
。见交互1a。 -
euroPaper
智能合约中的发行交易调用euroBond
智能合约中的查询交易。见交互点1b。 -
euroBond
查询可以从world state B
检索信息。参见交互点1c。 - 当控制权返回到
issue
交易时,它可以使用响应中的信息来定价票据并用信息更新world state A
。参见交互点1d。 - 发行以日元计价的商业票据的控制流程是相同的。参见交互点2a、2b、2c和2d。
控件是使用invokeCainCode()
API在链代码之间传递的。此API将控制权从一个链码传递到另一个链码。
虽然我们在示例中只讨论了查询交易,但是可以调用一个智能合约来更新被调用链码的世界状态。请参阅以下注意事项。
注意事项
- 一般来说,每个链码中都有一个智能合约。
- 如果多个智能合约关系非常密切,则只能在同一个链码中部署。通常,只有当它们共享同一个世界状态时,才有必要这样做。
- 链码命名空间提供不同世界状态之间的隔离。一般来说,将不相关的数据相互隔离是有意义的。请注意,您不能选择链码名称空间;它是由Hyperledger Fabric分配的,并直接映射到链码的名称。
- 对于使用
invokeCaincode()
API的链码到链码的交互,两个链码必须安装在同一节点上。
对于只需要查询被调用的链码的世界状态的交互,调用可以位于调用方的链码的不同通道中。
对于需要更新被调用链代码的世界状态的交互,调用必须与调用方的链代码处于同一通道中。
交易环境
受众:架构师、应用程序和智能合约开发人员
交易环境执行两个功能。首先,它允许开发人员在智能合约中跨交易调用定义和维护用户变量。其次,它提供了对各种Fabric APIs的访问,这些APIs允许智能合约开发人员执行与详细交易处理相关的操作。这些范围从查询或更新账本(不变的区块链和可修改的世界状态)到检索交易提交应用程序的数字身份。
当智能合约部署到通道并可用于后续的每个交易调用时,将创建交易环境。交易环境帮助智能合约开发人员编写功能强大、效率高、易于推理的程序。
- 为什么交易环境很重要
- 如何使用交易环境
- 在交易环境中是什么
-
使用环境
stub
-
使用环境
clientIdentity
方案
在商业票据样本中,papercontract最初定义了它负责的商业票据列表的名称。每个交易随后都会引用这个列表;发行交易会向其中添加新的票据,买入交易会更改其所有者,而赎回交易则会将其标记为完成。这是一种常见的模式;在编写智能合约时,初始化和调用顺序交易中的特定变量通常很有帮助。
智能合约交易环境允许智能合约跨交易调用定义和维护用户变量。详细解释见正文。
程序设计
构建智能合约时,开发人员可以选择重写内置Context
类createContext
方法来创建自定义环境:
createContext() {
new CommercialPaperContext();
}
在我们的示例中,CommercialPaperContext
专门用于CommercialPaperContract
。了解通过this
方法处理的自定义环境如何将特定变量PaperList
添加到自身中:
CommercialPaperContext extends Context {
constructor () {
this.paperList = new PaperList(this);
}
}
当createContext()方法在上图中的点(1)处返回时,将创建一个自定义上下文ctx,其中包含paperList
作为其变量之一。
随后,每当调用智能合约交易(如发行、买入或赎回)时,都会将此环境传递给它。请看第(2)、(3)和(4)点如何使用ctx
变量将相同的商业票据环境传递到transaction方法中。
看看context在第(5)点是如何使用的:
ctx.paperList.addPaper(...);
ctx.stub.putState(...);
请注意,在CommercialPaperContext
中创建的paperList
对于issue交易是如何可用的。看看paperList
在赎回和买入交易中是如何使用的;ctx
使智能合约高效且易于推理。
你还可以看到环境中还有另一个元素-ctx.stub
——这不是CommercialPaperContext
明确添加的。这是因为stub
和其他变量是内置环境的一部分。现在让我们检查一下这个内置环境的结构、这些隐式变量以及如何使用它们。
结构
正如我们从示例中看到的,交易环境可以包含任意数量的用户变量,例如paperList
。
交易环境还包含两个内置元素,它们提供对从提交交易的客户端应用程序到账本访问的各种结构功能的访问。
- ctx.stub用于访问提供广泛交易处理操作的API,从
putState()
和getState()
访问账本,到GetXid()
以检索当前交易ID。 -
ctx.clientIdentity
用于获取有关提交交易的用户身份的信息。
我们将使用下图向您展示一个智能合约可以使用stub
和clientIdentity
,并使用其可用的APIs来做什么:
智能合约可以通过交易环境stub
和clientIdentity
访问智能合约中的一系列功能。详细解释见正文。
Stub(存根)
存根中的APIs分为以下几类:
世界状态数据APIs。参见交互点(1)。这些APIs使智能合约能够使用其密钥从世界状态中获取、放置和删除与单个对象对应的状态:
这些基本APIs由查询APIs补充,查询APIs使合约能够检索一组状态,而不是单个状态。参见交互点(2)。该集合要么由一系列键值(使用完整或部分键)定义,要么根据底层世界状态数据库中的值进行查询。对于大型查询,可以对结果集进行分页,以减少存储需求:
- getStateByRange()
- getStateByRangeWithPagination()
- getStateByPartialCompositeKey()
- getStateByPartialCompositeKeyWithPagination()
- getQueryResult()
- getQueryResultWithPagination()
私有数据APIs。参见交互点(3)。这些APIs使智能合约能够与私有数据收集进行交互。它们类似于用于世界-国家交互的api,但用于私有数据。有一些API可以通过密钥获取、放置和删除私有数据状态:
这个集合由一组APIs来补充,以查询私有数据(4)。这些API允许智能合约根据一系列键值(完整或部分密钥),或根据底层世界状态数据库中的值进行查询,从私有数据集合中检索一组状态。当前没有用于私有数据收集的分页APIs。
交易APIs。参见交互点(5)。智能合约使用这些APIs检索智能合约正在处理的当前交易提案的详细信息。这包括交易标识符和创建交易提案的时间。
- getXid()返回当前交易提案的标识符(5)。
- getTextTimeStamp()返回应用程序创建当前交易提案时的时间戳(5)。
-
getCreator()返回事务建议创建者的原始标识(X.509或其他)。如果这是一个X.509证书,那么它通常更适合使用·
ctx.ClientIdentity
。 - getSignedProposal()返回智能合约正在处理的当前事务建议的签名副本。
- getBinding()用于防止交易被恶意或意外地使用nonce重放。(实际上,nonce是由客户端应用程序生成并包含在加密哈希中的随机数。)例如,此API可由位于(1)的智能合约用于检测事务(5)的重播。
- getTransient()允许智能合约访问应用程序传递给智能合约的瞬态数据。参见交互点(9)和(10)。瞬态数据对应用程序智能合约交互是私有的。它不记录在账本上,通常与私人数据收集一起使用(3)。
智能合约使用Key APIs来操纵世界状态或私有数据收集中的状态密钥。参见交互点2和4。
这些APIs中最简单的一个允许智能合约从它们各自的组件中形成和分割组合键。稍微高级一点的是ValidationParameter()
APIs,它为世界状态(2)和私有数据(4)获取和设置基于状态的背书策略。最后,getHistoryForKey()
通过返回一组存储值(包括执行状态更新的交易标识符)来检索状态的历史记录,从而允许从区块链读取交易(10)。
- createCompositeKey()
- splitCompositeKey()
- setStateValidationParameter()
- getStateValidationParameter()
- getPrivateDataValidationParameter()
- setPrivateDataValidationParameter()
- getHistoryForKey()
事件APIs用于管理智能合约中的事件处理。
- setEvent() 智能合约使用此API将用户事件添加到交易响应中。参见交互点(5)。这些事件最终记录在区块链上,并在交互点(11)发送到监听应用程序。
实用APIs是一组有用的APIs的集合,它们不容易归入预定义的类别中,因此我们将它们组合在一起!它们包括检索当前通道名和将控制权传递给同一节点上的不同链码。
- getChannelID() 见交互点(13)。在任何节点上运行的智能合约都可以使用此APIs来确定应用程序在哪个通道上调用了智能合约。
- invokeChaincode() 见交互点(14)。magneticorp拥有的Peer3上安装了多个智能合约。这些智能合约可以使用此API互相调用。智能合约必须并置;不可能在其他节点上调用智能合约。
其中一些实用API仅在使用低级链码时使用,而不是使用智能合约。这些api主要用于链码输入的详细操作;智能合约Contract
类为开发人员自动完成所有这些参数的编组。
客户端标识
在大多数情况下,提交交易的应用程序将使用X.509证书。在本例中,Isabella
(8)在申请中使用CA1
(7)颁发的X.509证书(6),以签署交易t6
(5)中的建议。
ClientIdentity
获取getCreator()
返回的信息,并在其上放置一组X.509实用APIs,以便更方便地用于此常见用例。
- getX509Certificate()返回交易提交者的完整X.509证书,包括其所有属性及其值。参见交互点(6)。
- getAttributeValue()返回特定X.509属性的值,例如组织单位OU或可分辨名称DN。参见交互点(6)。
- 如果X.509属性的指定属性具有指定值,则assertAttributeValue()返回
TRUE
。参见交互点(6)。 - getID()根据交易提交者的可分辨名称和颁发CA的可分辨名称,返回交易提交者的唯一标识。格式为
x509::{subject DN}::{issuer DN}
。参见交互点(6)。 - getMSPID()返回交易提交者的通道MSP。这允许智能合约根据提交者的组织身份做出处理决策。参见交互点(15)或(16)。
交易处理程序
受众:架构师、应用程序和智能合约开发人员
交易处理程序允许智能合约开发人员在应用程序和智能合约之间的交互期间在关键点定义公共处理。交易处理程序是可选的,但如果已定义,则它们将在智能合约中的每个交易被调用之前或之后接收控制。还有一个特定的处理程序,它在请求调用未在智能合约中定义的交易时接收控制权。
以下是商业票据智能合约示例的交易处理程序示例:
前、后和未知交易处理程序。在本例中,beforeTransaction()
在发出、买入和赎回交易之前被调用。afterTransaction()
在发行、买入和赎回交易之后调用。unknownTransaction()
仅在请求调用智能合约中未定义的交易时调用。(通过不对每个交易重复beforeTransaction
和afterTransaction
框,简化了图表。)
处理程序的类型
有三种类型的交易处理程序,它们涵盖应用程序与智能合约之间交互的不同方面:
- Before handler:在调用每个智能合约交易之前调用。处理程序通常会修改要由交易使用的交易环境。处理程序可以访问全套Fabric APIs;例如,它可以发出
getState()
和putState()
。 - After handler:在调用每个智能合约交易后调用。处理程序通常执行所有交易通用的后处理,并且还可以完全访问Fabric APIs。
- Unknown handler:如果试图调用未在智能合约中定义的交易,则调用。通常,处理程序将记录失败,以便管理员进行后续处理。处理程序具有对Fabric APIs的完全访问权限。
定义交易处理程序是可选的;智能合约在没有定义处理程序的情况下将正确执行。智能合约最多可以定义每种类型的一个处理程序。
定义处理程序
交易处理程序作为具有定义良好的名称的方法添加到智能合约中。下面是一个添加每种类型的处理程序的示例:
CommercialPaperContract extends Contract {
...
async beforeTransaction(ctx) {
// Write the transaction ID as an informational to the console
console.info(ctx.stub.getTxID());
};
async afterTransaction(ctx, result) {
// This handler interacts with the ledger
ctx.stub.cpList.putState(...);
};
async unknownTransaction(ctx) {
// This handler throws an exception
throw new Error('Unknown transaction function');
};
}
交易处理程序定义的形式对于所有处理程序类型都是相似的,但是请注意afterTransaction(ctx, result)
如何接收交易返回的任何结果。API文档显示了这些处理程序的确切形式。
处理程序处理
一旦将处理程序添加到智能合约中,将在交易处理期间调用它。在处理过程中,处理程序接收交易环境ctx
,执行一些处理,并在完成时返回控制。处理继续如下:
- Before handler:如果处理程序成功完成,则使用更新的环境调用交易。如果处理程序抛出异常,则不会调用交易,智能合约将失败,并显示异常错误消息。
- After handler:如果处理程序成功完成,则智能合约将按照调用的交易确定的方式完成。如果处理程序抛出异常,则交易失败并显示异常错误消息。
- Unknown handler:处理程序应通过引发异常并显示所需的错误消息来完成。如果未指定未知处理程序,或者未引发异常,则存在合理的默认处理;智能合约将失败,并显示未知交易错误消息。
如果处理程序需要访问函数和参数,则很容易做到:
async beforeTransaction(ctx) {
// Retrieve details of the transaction
let txnDetails = ctx.stub.getFunctionAndParameters();
console.info(`Calling function: ${txnDetails.fcn} `);
console.info(util.format(`Function arguments : %j ${stub.getArgs()} ``);
}
查看此处理程序如何通过交易环境使用实用API getFunctionAndParameters
。
多处理程序
对于智能合约,每种类型最多只能定义一个处理程序。如果智能合约需要在处理之前、之后或未知处理期间调用多个函数,则它应该在适当的函数内进行协调。
背书策略
受众:架构师、应用程序和智能合约开发人员
背书策略定义了为使交易有效而需要背书的最小组织集合。要进行背书,组织的背书节点需要运行与交易关联的智能合约并签署其结果。当排序服务将交易发送给确认节点时,它们将分别检查交易中的背书是否满足背书策略。如果不是这样,则交易将无效,并且不会对世界状态产生任何影响。
背书策略有两种不同的粒度:可以为整个命名空间以及单个状态键设置它们。它们是用基本的逻辑表达式如AND
和OR
来表示的。例如,在PaperNet中,这可以用如下方式使用:从MagnetoCorp出售给DigiBank的票据的背书政策可以设置为AND(MagnetoCorp.peer, DigiBank.peer)
,要求对票据的任何修改都必须得到magneticorp和DigiBank的认可。
连接配置文件
受众:架构师、应用程序和智能合约开发人员
连接配置文件描述了一组组件,包括Hyperledger Fabric区块链网络中的节点、排序节点和证书颁发机构。它还包含与这些组件相关的通道和组织信息。连接概要文件主要由应用程序用来配置处理所有网络交互的网关,使其能够专注于业务逻辑。连接配置文件通常由了解网络拓扑结构的管理员创建。
方案
网关用于配置配置文件。网关之所以重要有许多原因,主要是为了简化应用程序与网络通道的交互。
发行和买入两个应用程序使用配置了连接配置文件1和2的网关1和2。每个配置文件描述了magneticorp和DigiBank网络组件的不同子集。每个连接配置文件必须包含足够的信息,以便网关代表发行和购买应用程序与网络交互。详细解释见正文。
连接配置文件包含网络视图的描述,以技术语法表示,可以是JSON或YAML。在本主题中,我们使用YAML表示法,因为它更易于阅读。静态网关比动态网关需要更多的信息,因为后者可以使用服务发现来动态地增加连接配置文件中的信息。
连接配置文件不应该是对网络通道的详尽描述;它只需要包含足够的信息,以供使用它的网关使用。在上面的网络中,连接配置文件1需要至少包含issue
交易的背书组织和节点,以及标识将在交易提交到账本时通知网关的节点。
将连接配置文件看作是描述网络视图的最简单方法。这可能是一个全面的观点,但这是不现实的,原因如下:
- 节点、排序组织、证书颁发机构、通道和组织将根据需要添加和删除。
- 组件可能会启动和停止,或意外故障(例如停电)。
- 网关不需要查看整个网络,只需要成功处理交易提交或事件通知所需的内容。
- 服务发现可以增加连接配置文件中的信息。具体地说,动态网关可以配置为最少的Fabric拓扑信息;其余的可以被发现。
静态连接配置文件通常由详细了解网络拓扑结构的管理员创建。这是因为静态概要文件可以包含相当多的信息,管理员需要在相应的连接配置文件中捕获这些信息。相比之下,动态概要文件可以最大限度地减少所需的定义量,因此对于希望快速启动的开发人员或希望创建更具响应性的网关的管理员来说,是一个更好的选择。连接配置文件可以使用选择的编辑器以YAML或JSON格式创建。
用法
稍后我们将了解如何定义连接配置文件;让我们先看看它是如何被一个示例magneticorpissue
应用程序使用的:
const yaml = require('js-yaml');
const { Gateway } = require('fabric-network');
const connectionProfile = yaml.safeLoad(fs.readFileSync('../gateway/paperNet.yaml', 'utf8'));
const gateway = new Gateway();
await gateway.connect(connectionProfile, connectionOptions);
加载一些必需的类之后,请参见paperNet.yaml
文件网关文件从文件系统加载,并使用yaml.safeLoad()
方法,并用于使用其connect()
方法配置网关。
通过使用此连接配置文件配置网关,问题应用程序将向网关提供它应用于处理交易的相关网络拓扑。这是因为连接配置文件包含有关PaperNet通道、组织、节点、排序和CAs的足够信息,以确保交易能够成功处理。
对于一个连接配置文件来说,为任何给定的组织定义多个节点是一个很好的做法,它可以防止单点故障。此实践也适用于动态网关;为服务发现提供多个起点。
DigiBankbuy
应用程序通常会使用类似的连接配置文件来配置其网关,但有一些重要区别。有些元素将是相同的,例如通道;有些元素将重叠,例如背书的节点。其他元素将完全不同,例如通知节点或证书颁发机构。
传递给网关的connectionOptions
补充了连接配置文件。它们允许应用程序声明它希望网关如何使用连接配置文件。SDK将它们解释为控制与网络组件的交互模式,例如选择要连接的标识,或用于事件通知的节点。阅读可用连接选项列表以及何时使用它们。
结构
为了帮助您理解连接配置文件的结构,我们将逐步介绍上面所示网络的一个示例。它的连接配置文件基于PaperNet商业纸张示例,并存储在GitHub存储库中。为了方便起见,我们把它复制到下面。您将发现在另一个浏览器窗口中显示它很有帮助,因为您现在正在阅读它:
- 第9行:
name: "papernet.magnetocorp.profile.sample"
这是连接配置文件的名称。尽量使用DNS风格的名称;它们是传达意思的一种非常简单的方式。 - 第16行:
x-type: "hlfv1"
用户可以添加自己的“特定于应用程序”的x-
属性,就像HTTP头一样。它们主要供将来使用。 - 第20行:
description: "Sample connection profile for documentation topic"
连接配置文件的简短描述。试着让这对第一次看到这篇文章的读者有所帮助! - 第25行:
version: "1.0"
此连接配置文件的架构版本。目前只支持版本1.0,并且不认为此架构会频繁更改。 - 第32行:
channels:
这是第一条非常重要的路线。channels:
标识以下是此连接配置文件描述的所有通道。但是,在不同的连接配置文件中保持不同的通道是一个很好的做法,特别是当它们彼此独立使用时。 - 第36行:
papernet:
papernet
是这个连接配置文件中的第一个通道,详细信息如下。 - 第41行:
orderers:
papernet
的所有排序者的详细信息如下。您可以在第45行看到这个通道的排序者是orderer1.magnetocorp.example.com
。这只是一个逻辑名称;稍后在连接配置文件(第134-147行)中,将详细介绍如何连接到该排序程序。注意orderer2.digibank.example.com
不在此列表中;应用程序使用自己组织的排序器,而不是来自不同组织的排序器,这是有意义的。 - 第49行:
peers:
papernet
的所有节点的详细信息如下。
你可以看到来自magneticorp的三个节点:peer1.magnetocorp.example.com
,peer2.magnetocorp.example.com
还有peer3.magnetocorp.example.com
。没有必要像这里所做的那样列出magneticorp的所有节点。您只能看到DigiBank中列出的一个节点:peer9.digibank.example.com
;包括这位节点开始表示,背书策略要求magneticorp和DigiBank为交易背书,我们现在将予以确认。最好有多个节点来避免单点失败。
在每个peer下面可以看到四个非独占角色:endorsingPeer、chaincodeQuery、ledgerQuery和eventSource。了解peer1
和peer2
在承载papercontract
时如何执行所有角色。与peer3
不同,peer3
只能用于通知或账本查询,后者访问账本的区块链组件,而不需要安装智能合约。请注意peer9
不应该被用于除了背书之外的任何事情,因为这些角色由magneticorp的节点更好地服务。
同样,看看如何根据节点的逻辑名称和角色来描述它们。在稍后的配置文件中,我们将看到这些节点的物理信息。 - 第97行:
organizations:
所有通道的所有组织的详细信息都将跟进。请注意,这些组织适用于所有通道,尽管papernet
是目前唯一列出的一家。这是因为组织可以有多个通道,通道可以有多个组织。此外,有些应用程序操作与组织有关,而不是与通道有关。例如,应用程序可以使用连接选项从其组织内的一个或所有节点或网络中的所有组织请求通知。为此,需要有一个组织到节点的映射,本节将提供这种映射。 - 第101行:
MagnetoCorp:
所有被视为magneticorp一部分的节点都被列出:peer1
、peer2
和peer3
。对于证书颁发机构也是如此。同样,请注意逻辑名称的用法,与channels:
section相同;物理信息将在概要文件的后面部分显示。 - 第121行:
DigiBank:
只有peer9
被列为DigiBank的一部分,没有证书颁发机构。这是因为这些其他节点和DigiBank CA与此连接配置文件的用户无关。 - 第134行:
orderers:
现在列出了排序者的物理信息。由于此连接配置文件只提到papernet
的一个排序程序,因此您可以看到orderer1.magnetocorp.example.com
列出详细信息。其中包括其IP地址和端口,以及gRPC选项,这些选项可以在必要时覆盖与排序节点通信时使用的默认值。与普通节点一样,为了获得高可用性,指定多个排序节点是一个好主意。 - 第152行:
peers:
现在列出了所有以前节点的物理信息。对于magneticorp,这个连接配置文件有三个节点:peer1
、peer2
和peer3
;对于DigiBank,一个节点peer9
列出了它的信息。对于每个普通节点,与排序节点一样,列出了它们的IP地址和端口,以及可以在必要时覆盖与特定节点通信时使用的默认值的gRPC选项。 - 第194行:
certificateAuthorities:
现在列出了证书颁发机构的物理信息。连接配置文件为magneticorp列出了一个CA,即ca1-magneticorp
,其物理信息如下所示。除了IP详细信息外,注册信息还允许将此CA用于证书签名请求(CSR)。它们用于为本地生成的公钥/私钥对请求新证书。
现在您已经了解了magneticorp的连接配置文件,您可能需要查看DigiBank的相应配置文件。找到与magneticorp的剖面图相同的地方,看看它们有什么相似之处,最后找出它们的不同之处。想想为什么这些差异对DigiBank应用程序有意义。
这就是你需要知道的关于连接配置文件的所有信息。总之,连接配置文件为应用程序配置网关定义了足够的通道、组织、普通节点、排序节点和证书颁发机构。网关允许应用程序关注业务逻辑,而不是网络拓扑的细节。
样本
此文件是从GitHub商业文件示例内联复制的。
---
#
# [Required]. A connection profile contains information about a set of network
# components. It is typically used to configure gateway, allowing applications
# interact with a network channel without worrying about the underlying
# topology. A connection profile is normally created by an administrator who
# understands this topology.
#
name: "papernet.magnetocorp.profile.sample"
#
# [Optional]. Analogous to HTTP, properties with an "x-" prefix are deemed
# "application-specific", and ignored by the gateway. For example, property
# "x-type" with value "hlfv1" was originally used to identify a connection
# profile for Fabric 1.x rather than 0.x.
#
x-type: "hlfv1"
#
# [Required]. A short description of the connection profile
#
description: "Sample connection profile for documentation topic"
#
# [Required]. Connection profile schema version. Used by the gateway to
# interpret these data.
#
version: "1.0"
#
# [Optional]. A logical description of each network channel; its peer and
# orderer names and their roles within the channel. The physical details of
# these components (e.g. peer IP addresses) will be specified later in the
# profile; we focus first on the logical, and then the physical.
#
channels:
#
# [Optional]. papernet is the only channel in this connection profile
#
papernet:
#
# [Optional]. Channel orderers for PaperNet. Details of how to connect to
# them is specified later, under the physical "orderers:" section
#
orderers:
#
# [Required]. Orderer logical name
#
- orderer1.magnetocorp.example.com
#
# [Optional]. Peers and their roles
#
peers:
#
# [Required]. Peer logical name
#
peer1.magnetocorp.example.com:
#
# [Optional]. Is this an endorsing peer? (It must have chaincode
# installed.) Default: true
#
endorsingPeer: true
#
# [Optional]. Is this peer used for query? (It must have chaincode
# installed.) Default: true
#
chaincodeQuery: true
#
# [Optional]. Is this peer used for non-chaincode queries? All peers
# support these types of queries, which include queryBlock(),
# queryTransaction(), etc. Default: true
#
ledgerQuery: true
#
# [Optional]. Is this peer used as an event hub? All peers can produce
# events. Default: true
#
eventSource: true
#
peer2.magnetocorp.example.com:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true
#
peer3.magnetocorp.example.com:
endorsingPeer: false
chaincodeQuery: false
ledgerQuery: true
eventSource: true
#
peer9.digibank.example.com:
endorsingPeer: true
chaincodeQuery: false
ledgerQuery: false
eventSource: false
#
# [Required]. List of organizations for all channels. At least one organization
# is required.
#
organizations:
#
# [Required]. Organizational information for MagnetoCorp
#
MagnetoCorp:
#
# [Required]. The MSPID used to identify MagnetoCorp
#
mspid: MagnetoCorpMSP
#
# [Required]. The MagnetoCorp peers
#
peers:
- peer1.magnetocorp.example.com
- peer2.magnetocorp.example.com
- peer3.magnetocorp.example.com
#
# [Optional]. Fabric-CA Certificate Authorities.
#
certificateAuthorities:
- ca-magnetocorp
#
# [Optional]. Organizational information for DigiBank
#
DigiBank:
#
# [Required]. The MSPID used to identify DigiBank
#
mspid: DigiBankMSP
#
# [Required]. The DigiBank peers
#
peers:
- peer9.digibank.example.com
#
# [Optional]. Orderer physical information, by orderer name
#
orderers:
#
# [Required]. Name of MagnetoCorp orderer
#
orderer1.magnetocorp.example.com:
#
# [Required]. This orderer's IP address
#
url: grpc://localhost:7050
#
# [Optional]. gRPC connection properties used for communication
#
grpcOptions:
ssl-target-name-override: orderer1.magnetocorp.example.com
#
# [Required]. Peer physical information, by peer name. At least one peer is
# required.
#
peers:
#
# [Required]. First MagetoCorp peer physical properties
#
peer1.magnetocorp.example.com:
#
# [Required]. Peer's IP address
#
url: grpc://localhost:7151
#
# [Optional]. gRPC connection properties used for communication
#
grpcOptions:
ssl-target-name-override: peer1.magnetocorp.example.com
request-timeout: 120001
#
# [Optional]. Other MagnetoCorp peers
#
peer2.magnetocorp.example.com:
url: grpc://localhost:7251
grpcOptions:
ssl-target-name-override: peer2.magnetocorp.example.com
request-timeout: 120001
#
peer3.magnetocorp.example.com:
url: grpc://localhost:7351
grpcOptions:
ssl-target-name-override: peer3.magnetocorp.example.com
request-timeout: 120001
#
# [Required]. Digibank peer physical properties
#
peer9.digibank.example.com:
url: grpc://localhost:7951
grpcOptions:
ssl-target-name-override: peer9.digibank.example.com
request-timeout: 120001
#
# [Optional]. Fabric-CA Certificate Authority physical information, by name.
# This information can be used to (e.g.) enroll new users. Communication is via
# REST, hence options relate to HTTP rather than gRPC.
#
certificateAuthorities:
#
# [Required]. MagnetoCorp CA
#
ca1-magnetocorp:
#
# [Required]. CA IP address
#
url: http://localhost:7054
#
# [Optioanl]. HTTP connection properties used for communication
#
httpOptions:
verify: false
#
# [Optional]. Fabric-CA supports Certificate Signing Requests (CSRs). A
# registrar is needed to enroll new users.
#
registrar:
- enrollId: admin
enrollSecret: adminpw
#
# [Optional]. The name of the CA.
#
caName: ca-magnetocorp
参考自官方文档 如有侵权,请联系作者删除,谢谢!
If there is infringement, please contact the author to delete, thank you!