Fabric 入门
Fabric
1. 关键概念
1.1 区块链网络
这个 Fabric 区块链网络包括了两个应用程序通道以及一个排序通道。
组织 R1 和 R4 负责排序通道,R1 和 R2 负责蓝色的应用程序通道,R2 和 R3 负责红色的应用程序通道。
客户端应用程序 A1 是组织 R1 的元素,CA1 是它的证书颁发机构。
组织 R2 的节点 P2 可以使用蓝色的通信设施,也可以使用红色的应用程序通道。
每个应用程序通道具有它自己的通道配置,这里是 CC1 和 CC2。
系统通道的通道配置是网络配置 NC4 的一部分。
一个有四个组织的网络,带有两个通道和三个 Peer 节点,两个智能合约和一个排序服务。
并由四个证书颁发机构来支撑。
它为三个客户端应用程序提供了账本及智能合约服务,这些应用程序可以通过两个通道与账本和智能合约进行交互。
1.2 身份
- 确定了对资源的确切权限以及对参与者在区块链网络中拥有的信息的访问权限
- 要使身份可以被验证,它必须来自可信任的权威机构
- 成员服务提供者(Membership Service Provider,MSP)是 Fabirc 中可以信任的权威机构
- MSP 将可验证的身份转变为区块链网络的成员
1.2.1 PKI
- 公钥基础结构(PKI)是一组互联网技术,可在网络中提供安全通信
- 关键要素
- 数字证书
- 公钥和私钥
- 证书授权中心
- 证书撤销列表
1.3 成员服务提供者 (MSP)
以太坊属于匿名网络,hpyerledger 是实名制的网络
1.4 Peer 节点
区块链网络主要由 Peer 节点(或者简单称之为 Peer)组成
区块链网络是由 Peer 节点组成的,每个节点都保存着账本和智能合约的副本。
在这个例子中,网络 N 是由节点 P1、P2 和 P3 组成的,每个节点都维护这他们自己的分布式账本 L1。P1、P2 和 P3 使用相同的链码 S1 来访问他们的分布式账本的副本。
在 Fabric 中,链码等同于智能合约,因为它们是使用一个被称为链码的技术概念来实现智能合约的
Peer 节点是账本及链码的宿主,应用程序及管理员如果想要访问这些资源,他们必须要和 Peer 节点进行交互
1.4.1 多账本
- 一个 Peer 节点可以维护多个账本,并且每个账本具有零个或者多个链码使用账本
在这个例子中,我们能够看到 Peer 节点 P1 维护着账本 L1 和 L2。账本 L1 通过链码 S1 来访问。账本 2 通过链码 S1 和 S2 访问
1.4.2 多链码
- 账本数量和访问账本的链码的数量之间没有固定的关系。一个 Peer 节点可能会有很多链码和账本
1.4.3 应用程序和 Peer 节点
1.4.4 Peer 节点和排序节点
一个更新的交易和一个查询的交易区别很大,因为一个单独的 Peer 节点不能够由它自己来更新账本——更新需要网络中其他节点的同意
在一个账本的更新被应用到 Peer 节点的本地账本之前, Peer 节点会请求网络中的其他 Peer 节点来批准这次更新。这个过程被称为共识,这会比一个简单的查询花费更长的时间来完成
想要更新账本的应用程序会被引入到一个三阶段的流程,这确保了在一个区块链网络中所有的 Peer 节点都彼此保持着一致的账本。
- 在第一个阶段,应用程序会跟背书节点的子集一起工作,其中的每个节点都会向应用程序为提案的账本更新提供背书,但是不会将提案的更新应用到他们的账本副本上。
- 在第二个阶段,这些分散的背书会被搜集到一起当做交易被打包进区块中。
- 在最后一个阶段,这些区块会被分发回每个 Peer 节点,在这些 Peer 节点上每笔交易在被应用到 Peer 节点的账本副本之前会被验证。
排序节点在这个流程中处于中心地位
1.5 chaincode 智能合约
- 智能合约用可执行的代码定义了不同组织之间的规则。应用程序调用智能合约来生成被记录到账本上的交易。
在上图中,我们可以看到组织
ORG1
和ORG2
是如何通过定义一个car
智能合约来实现查询
、转移
和更新
汽车的。来自这些组织的应用程序调用此智能合约执行业务流程中已商定的步骤,例如将特定汽车的所有权从ORG1
转移到ORG2
可以将智能合约看成交易的管理者,而链码则管理着如何将智能合约打包以便用于部署。
一个智能合约定义在一个链码中。而多个智能合约也可以定义在同一个链码中。当一个链码部署完毕,该链码中的所有智能合约都可供应用程序使用。
- 智能合约的核心是一组
交易
定义。例如,在fabcar.js
中,你可以看到一个创建了一辆新车的智能合约交易:
1 | async createCar(ctx, carNumber, make, model, color, owner) { |
1.5.1 背书
- 每个链码都有一个背书策略与之相关联,该背书策略适用于此链码中定义的所有智能合约。背书策略非常重要,它指明了区块链网络中哪些组织必须对一个给定的智能合约所生成的交易进行签名,以此来宣布该交易有效。
- 一个示例背书策略可能这样定义:参与区块链网络的四个组织中有三个必须在交易被认为有效之前签署该交易。所有的交易,无论是有效的还是无效的,都会被添加到分布式账本中,但只有有效交易会更新世界状态。
- 背书策略是 Hyperledger Fabric 与以太坊(Ethereum)或比特币(Bitcoin)等其他区块链的区别所在
1.5.2 有效交易
注意,在执行智能合约时世界状态没有更新!
所有的交易都有一个识别符、一个提案和一个被一群组织签名的响应。所有交易,无论是否有效,都会被记录在区块链上,但仅有效交易会更新世界状态。
有效交易举例:
检查
车辆转移
交易。您可以看到ORG1
和ORG2
之间为转移一辆车而进行的交易t3
。看一下交易是如何通过输入{CAR1,ORG1,ORG2}
和输出{CAR1.owner=ORG1,CAR1.owner=ORG2}
来表示汽车的所有者从ORG1
变为了ORG2
。注意输入是如何由应用程序的组织ORG1
签名的,输出是如何由背书策略标识的两个组织(ORG1
和ORG2
)签名的。这些签名是使用每个参与者的私钥生成的,这意味着网络中的任何人都可以验证网络中的所有参与者是否在交易细节上达成了一致一项交易被分发给网络中的所有节点,各节点通过两个阶段对其进行验证。首先,根据背书策略检查交易,确保该交易已被足够的组织签署。其次,继续检查交易,以确保当该交易在受到背书节点签名时它的交易读集与世界状态的当前值匹配,并且中间过程中没有被更新。如果一个交易通过了这两个测试,它就被标记为有效。所有交易,不管是有效的还是无效的,都会被添加到区块链历史中,但是仅有效的交易才会更新世界状态。
1.5.3 通道
- Hyperledger Fabric 允许一个组织利用通道同时参与多个、彼此独立的区块链网络。通过加入多个通道,一个组织可以参与一个所谓的网络的网络
- 通道在一群组织之间提供了一种完全独立的通信机制。当链码定义被提交到通道上时,该通道上所有的应用程序都可以使用此链码中的智能合约。
在上面的示例中,
car
智能合约被定义在VEHICLE
通道上,insurance
智能合约被定义在INSURANCE
通道上。car
的链码定义明确了以下背书策略:任何交易在被认定为有效之前必须由ORG1
和ORG2
共同签名。insurance
智能合约的链码定义明确了只需要ORG3
对交易进行背书即可。ORG1
参与了VEHICLE
通道和INSURANCE
通道这两个网络,并且能够跨网络协调与ORG2
和ORG3
的活动。
1.6 账本
- 由“世界状态“和”区块链“这两部分组成
- 世界状态是一个数据库,它存储了一组账本状态的当前值,通过世界状态,程序可以直接访问一个账本状态的当前值,不需要遍历整个交易日志来计算当前值
- 区块链是交易日志,它记录了促成当前世界状态的所有改变
账本 L 由区块链 B 和世界状态 W 组成,其中世界状态 W 由区块链 B 决定。我们也可以说世界状态 W 是源自区块链 B
1.6.1 世界状态
- 世界状态被作为数据库来实现
示例展示的是 CAR1 和 CAR2 这两辆车的账本状态,二者都各有一个值和一个键。应用程序可以调用智能合约,该合约使用简单的账本 API 来获取、写入和删除状态。注意状态值可以是简单值(Audi…),也可以是复合值(type:BMW…)。经常会通过查询世界状态来检索具有某些特定属性的对象,例如查找所有红色宝马汽车。
- 应用程序提交那些会更改世界状态的交易,这些交易最终被提交到账本区块链上。
- 应用程序无法看到 Hyperledger Fabric SDK(软件开发工具包)设定的共识机制的细节内容,它们能做的只是调用智能合约以及在交易被收进区块链时收到通知(所有被提交的交易,无论有效与否,都会被收进区块链),但是只有那些受到相关背书组织签名的交易才会更新世界状态。如果一个交易没有得到足够背书节点的签名,那么它不会更新世界状态
- 每个状态都有一个版本号,版本号是供 Hyperledger Fabric 内部使用的,并且每次状态更改时版本号会发生递增。每当更新状态时,都会检查该状态的版本,以确保当前状态与背书时的版本相匹配。
1.6.2 区块链
- 世界状态存储了与业务对象当前状态相关的事实信息
- 而区块链是一种历史记录,它记录了这些业务对象是如何到达各自当前状态的相关事实。区块链记录了每个账本状态之前的所有版本以及状态是如何被更改的
- 区块链的结构是一群相互链接的区块的序列化日志,其中每个区块都包含一系列交易,各项交易代表了一个对世界状态进行的查询或更新操作
- 区块链总是以文件实现,而与之相反的是,世界状态以数据库实现
B0 是该区块链的第一个区块,也叫创世区块
它并不包含任何用户交易,但却是账本的起始点
相反的,创世区块包含了一个配置交易,该交易含有网络配置(未显示)的初始状态
1.6.3 区块
组成
区块头
- 区块编号:编号从0(初始区块)开始,每在区块链上增加一个新区块,编号的数字都会加1。
- 当前区块的哈希值:当前区块中包含的所有交易的哈希值。
- 前一个区块头的哈希值:区块链中前一个区块头的哈希值。
区块头详情:区块 B2 的区块头 H2 包含了区块编号 2,当前区块数据 D2 的哈希值 CH2,以及前一个区块头 H1 的哈希值。
区块数据
- 这部分包含了一个有序的交易列表。区块数据是在排序服务创建区块时被写入的
区块元数据
- 这个部分包含了区块被写入的时间,还有区块写入者的证书、公钥以及签名。随后,区块的提交者也会为每一笔交易添加一个有效或无效的标记,但由于这一信息与区块同时产生,所以它不会被包含在哈希中。
1.6.4 交易
- 交易记录了世界状态发生的更新
- 把交易包含在区块中的区块数据结构
交易详情:交易 T4 位于区块 B1 的区块数据 D1 中,T4包括的内容如下:交易头 H4,一个交易签名 S4,一个交易提案 P4,一个交易响应 R4 和一系列背书 E4。
头
这部分用 H4 表示,它记录了关于交易的一些重要元数据,比如,相关链码的名字以及版本。
签名
这部分用 S4 表示,它包含了一个由客户端应用程序创建的加密签名。该字段是用来检查交易细节是否未经篡改,因为交易签名的生成需要用到应用程序的私钥。
提案
这部分用 P4 表示,它负责对应用程序供给智能合约的输入参数进行编码,随后该智能合约生成提案账本更新。在智能合约运行时,这个提案提供了一套输入参数,这些参数同当前的世界状态一起决定了新的账本世界状态。
响应
这部分用 R4 表示,它是以读写集 (RW-set)的形式记录下世界状态之前和之后的值。交易响应是智能合约的输出,如果交易验证成功,那么该交易会被应用到账本上,从而更新世界状态。
背书
示例账本
账本 L包含了一个世界状态 W 和一个区块链 B。其中 W 包含了四个状态,各状态的键分别是:CAR0,CAR1,CAR2 和 CAR3 。而 B 包含了两个区块 0和 1。区块1包含了四笔交易:T1,T2,T3,T4
1.7 排序服务
- Hyperledger Fabric 的工作方式不同。它有一种称为排序节点的节点使交易有序,并与其他排序节点一起形成一个排序服务。因为 Fabric 的设计依赖于确定性的共识算法,所以 Peer 节点所验证的区块都是最终的和正确的。账本不会像其他分布式的以及无需许可的区块链中那样产生分叉
- 排序节点还维护着允许创建通道的组织列表。此组织列表称为“联盟”
- 排序节点还对通道执行基本访问控制,限制谁可以读写数据,以及谁可以配置数据
1.7.1 排序节点和交易流程
更新账本的应用程序涉及到三个阶段,该过程确保区块链网络中的所有节点保持它们的账本彼此一致。
在第一阶段,客户端应用程序将交易提案发送给一组节点,这些节点将调用智能合约来生成一个账本更新提案,然后背书该结果。背书节点此时不将提案中的更新应用于其账本副本。相反,背书节点将向客户端应用程序返回一个提案响应。
已背书的交易提案最终将在第二阶段经过排序生成区块,
然后在第三阶段分发给所有节点进行最终验证和提交。
- 将交易排序并打包到区块中
- 在此阶段,应用程序客户端把包含已背书交易提案响应的交易提交到排序服务节点。排序服务创建交易区块,这些交易区块最终将分发给通道上的所有 Peer 节点,以便在第三阶段进行最终验证和提交。
- 排序服务节点的工作是将提交的交易按定义好的顺序安排成批次,并将它们打包成区块。这些区块将成为区块链的区块!
排序节点的第一个角色是打包提案的账本更新。在本例中,应用程序 A1 向排序节点 O1 发送由 E1 和 E2 背书的交易 T1。同时,应用程序 A2 将 E1 背书的交易 T2 发送给排序节点 O1。O1 将来自应用程序 A1 的交易 T1 和来自应用程序 A2 的交易 T2 以及来自网络中其他应用程序的交易打包到区块 B2 中。我们可以看到,在 B2 中,交易顺序是 T1、T2、T3、T4、T6、T5,但这可能不是这些交易到达排序节点的顺序!(这个例子显示了一个非常简单的排序服务配置,只有一个排序节点。)
- 验证和提交
排序节点的第二个角色是将区块分发给 Peer 节点。在本例中,排序节点 O1 将区块 B2 分配给节点 P1 和 P2。节点 P1 处理区块 B2,在 P1 上的账本 L1 中添加一个新区块。同时,节点 P2 处理区块 B2,从而将一个新区块添加到 P2 上的账本 L1中。一旦这个过程完成,节点 P1 和 P2 上的账本 L1 就会保持一致的更新,并且每个节点都可以通知与之连接的应用程序交易已经被处理。
1.7.2 排序服务的实现
- 推荐 Raft
- Raft 是一种基于
etcd
中 Raft 协议实现的崩溃容错(Crash Fault Tolerant,CFT)排序服务。Raft 遵循“领导者跟随者”模型,这个模型中,在每个通道上选举领导者节点,其决策被跟随者复制。Raft 排序服务会比基于 Kafka 的排序服务更容易设置和管理,它的设计允许不同的组织为分布式排序服务贡献节点。 - 节点总是处于以下三种状态之一:跟随者、候选人或领导者。所有节点最初都是作为跟随者开始的。在这种状态下,他们可以接受来自领导者的日志条目(如果其中一个已经当选),或者为领导者投票。如果在一段时间内没有接收到日志条目或心跳(例如,5秒),节点将自己提升到候选状态。在候选状态中,节点从其他节点请求选票。如果候选人获得法定人数的选票,那么他就被提升为领导者。领导者必须接受新的日志条目并将其复制到跟随者。
- Raft 是一种基于
2. 入门
2.1 配置环境
此处参考b站视频,虽然可能不全,并且后半段实现不了
但是前面的跟下来都还不错!但是要注意自己的版本问题!
2.1.1 ubuntu 联网
1 | sudo systemctl restart NetworkManager.service |
2.1.2 apt 换源
1 | https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/ |
2.1.3 安装 docker
1 | // 安装docker、docker-compose |
2.1.4 安装 golang
1 | // 安装golang |
2.1.5 docker 加速器
1 | // docker加速器 |
2.1.6 安装fabric-sample
1 | 1、手动创建脚本,安装samples、docker |
3. 开发
3.1 分析
3.1.1 商业票据
票据 00001 是 5 月 31 号由 MagnetoCorp 发行的。该票据的第一个状态,它具有不同的属性和值:
1
2
3
4
5
6
7Issuer = MagnetoCorp
Paper = 00001
Owner = MagnetoCorp
Issue date = 31 May 2020
Maturity = 30 November 2020
Face value = 5M USD
Current state = issued票据的状态是 发行 交易的结果,它使得 MagnetoCorp 公司的第一张商业票据面世!注意该票据在今年晚些时候如何兑换面值 500 万美元。当票据 00001 发行后
Issuer
和Owner
具有相同的值。该票据有唯一标识MagnetoCorp00001
——它是Issuer
属性和Paper
属性的组合。最后,属性Current state = issued
快速识别了 MagnetoCorp 票据 00001 在它生命周期中的阶段。发行后不久,该票据被 DigiBank 购买。由于购买交易,同一个商业票据如何发生变化:
1
2
3
4
5
6
7Issuer = MagnetoCorp
Paper = 00001
Owner = DigiBank
Issue date = 31 May 2020
Maturity date = 30 November 2020
Face value = 5M USD
Current state = trading最重要的变化是
Owner
的改变——票据初始拥有者是MagnetoCorp
而现在是DigiBank
。我们可以想象该票据后来如何被出售给 BrokerHouse 或 HedgeMatic,以及相应的变更为相应的Owner
。注意Current state
允许我们轻松的识别该票据目前状态是trading
。6 个月后,如果 DigiBank 仍然持有商业票据,它就可以从 MagnetoCorp 那里兑换:
1
2
3
4
5
6
7Issuer = MagnetoCorp
Paper = 00001
Owner = MagnetoCorp
Issue date = 31 May 2020
Maturity date = 30 November 2020
Face value = 5M USD
Current state = redeemed最终的兑换交易结束了这个商业票据的生命周期——它可以被认为票据已经终止。通常必须保留已兑换的商业票据的记录,并且
redeemed
状态允许我们快速识别这些。通过将Owner
跟交易创建者的身份进行比较,一个票据的Owner
值可以被用来在兑换交易上进行访问控制
3.1.2 交易
我们已经看到票据 00001 的生命周期相对简单——由于发行,购买和兑换交易,它在
issued
,trading
和redeemed
状态之间转移注意交易和票据不同!!
- 发行交易
1
2
3
4
5
6Txn = issue
Issuer = MagnetoCorp
Paper = 00001
Issue time = 31 May 2020 09:00:00 EST
Maturity date = 30 November 2020
Face value = 5M USD- 购买交易
1
2
3
4
5
6
7Txn = buy
Issuer = MagnetoCorp
Paper = 00001
Current owner = MagnetoCorp
New owner = DigiBank
Purchase time = 31 May 2020 10:00:00 EST
Price = 4.94M USD- 兑换交易
1
2
3
4
5Txn = redeem
Issuer = MagnetoCorp
Paper = 00001
Current owner = HedgeMatic
Redeem time = 30 Nov 2020 12:00:00 EST
3.2 流程和数据设计
3.2.1 生命周期
在处理商业票据时有两个重要的概念:状态和交易
对状态和交易的有效分析是成功实施的重要起点,可以用状态转移表来表示商业票据的生命周期:
商业票据的状态转移表。商业票据通过发行,购买和兑换交易在已发行、交易中和已兑换之间进行状态转移。
3.2.2 账本状态
商业票据的结构:
商业票据可以被表示为属性集,每个属性都对应一个值。通常,这些属性的组合会为每个票据提供一个唯一键
结合来看,属性的完整集合构成了商业票据的状态。此外,这些商业票据的全部集合构成了账本的世界状态。
查看 MagnetoCorp 的票据
00001
如何表示为一个状态向量,根据不同的交易刺激进行转换:注意每个独立的票据都起于空状态,技术上被称作 nil,来表示票据不存在!通过发行交易,票据
00001
问世,然后由于购买和兑换交易而更新状态
3.2.3 状态键值
- 大多数的实际应用中,状态会有一个属性组合在给定的上下文中唯一识别它——它就是主键
- PaperNet 商业票据的主键是通过
Issuer
属性和paper
属性拼接得到的,所以 MagnetoCorp 的第一个票据的主键就是MagnetoCorp00001
- Fabric 需要账本中的每个状态都有唯一的主键
- 当唯一主键在可用的属性集中不能获得,应用决定的唯一键会被指定为交易的输入来创建状态。这个唯一键的形式一般是 UUID
3.2.3 逻辑表示
为了满足不同类型的查询任务,把所有相关的商业票据按逻辑顺序排列在一起是很有帮助的。PaperNet 的设计包含了商业票据列表的思想——一个逻辑容器,每当商业票据发行或发生其他更改时,该容器都会更新
把所有的 PaperNet 商业票据放在一个商业票据列表中是有帮助的:
新票据由于发行交易被加入到列表中,然后列表中已存在的票据因为购买交易和兑换交易可以被更新状态。列表有一个描述性的名称:
org.papernet.papers
;使用这种DNS 名真的是一个好主意,因为适当的名称会让你的区块链设计对其他人来说是直观的。这种想法同样也适用于智能合约的名字。
3.2.4 物理表现
我们可以正确地想到 PaperNet 中的单个票据列表——
org.papernet.papers
——列表最好作为一组单独的 Fabric 状态来实现,其复合键将状态与其列表关联起来。这样,每个状态的复合键都是惟一的,并支持有效的列表查询状态向量的物理设计对于优化性能和行为非常重要。保持状态的独立!
3.3 智能合约处理
连接到网络的所有应用程序必须使用相同版本的智能合约,以便它们共同实现相同的共享业务流程和数据。
在 Java 中,类必须使用
@Contract(...)
标注进行包装。它支持额外的智能合约信息,比如许可和作者。@Default()
标注表明该智能合约是默认合约类。在智能合约中标记默认合约类在一些有多个合约类的智能合约中会很有用。fabric-samples/CommercialPaperContract.java at master · hyperledger/fabric-samples (github.com)
1 | /* |
3.4 应用
3.4.1 基本流程
应用程序如何调用商业票据智能合约的简化图表:
应用程序必须遵循六个基本步骤来提交交易:
- 从钱包中选择一个身份
- 连接到网关
- 访问所需的网络
- 构建智能合约的交易请求
- 将交易提交到网络
- 处理响应