区块链开发实战特训营:深入掌握HyperLedger(超级账本)Fabric的开发技术

发布时间 2023-10-26 17:25:58作者: 天使angl

区块链开发实战特训营:深入掌握HyperLedger(超级账本)Fabric的开发技术

hyperledger fabric是区块链中联盟链的优秀实现,主要代码由IBM、Intel、各大银行等贡献,目前v1.1版的kafka共识方式可达到1000/s次的吞吐量。

架构说明
Hyperledger Fabric带来如下优势:

链码信任灵活性。架构将对链码(区块链应用程序)的信任假设与对排序的信任假设相分离。换种说法,排序服务可以由一组节点(排序者)提供并容忍其中一部分失败或腐化,每个链码可以有不同的背书者。
可扩展性。由于负责特定链码的背书者与排序者不相交,系统可以比由相同节点完成背书和排序这两个功能更易扩展。特别是,当不同的链码指定不同的背书者时,导致了不同背书者之间的链码的分区,并且允许并行执行链码。此外,执行链码的资源消耗可能很大,于是让其与关键的排序服务分开单独部署。
保密。架构有助于部署对交易的内容和状态更新有保密要求的链码。
共识模块化。架构是模块化的并允许可插拔的共识(比如排序服务)实现。
第一部分:Hyperledger Fabric v1系统架构元素
1. 系统架构
2. 交易背书的基本工作流
3. 背书策略

第二部分:V1后版本的架构元素
4. 账本检查点(修订中)

  1. 系统架构
    区块链是一个由许多节点互相通信组成的分布式系统。区块链运行称为链码的应用程序,保存状态和账本数据,执行交易。因为交易是在链码上调用的操作,因而链码是区块链结构的中心元素。交易必须被“背书”而且只有背书过的交易才可以被承认和对状态造成影响。有一个或多个用于管理功能(函数)和参数的特殊的链码,它们统称为系统链码。

1.1. 交易
交易有两种类型:
- 部署交易 创建新链码并以一个程序作为参数。当一个部署链码成功执行以后,链码被安装在区块链“上”。
- 调用交易 在之前部署的链码的上下文中执行操作。一笔调用交易指的是一个链码和他提供的若干功能中的一个功能。如果调用交易成功,链码执行了特定的函数,这个函数可能涉及修改相应的状态,然后返回一个输出。

备注:本文档目前假定一笔交易要么创建新链码要么调用一个由一个已经部署的链码提供的操作。本文档尚未阐述关于:a) 请求(只读)交易的优化(V1版本),b) 跨链码交易的支持(V1后版本)

后面会讲到,部署交易是调用交易的一种特例,部署交易创建新的链码,对应于一个系统链码上的调用交易。

1.2. 区块链数据结构
1.2.1. 状态(state)
区块链的最新状态被建模为一个版本化的键值存储(KVS),键是名字,值是任意二进制大对象(blobs)。这些键值对被运行在区块链上的链码(应用程序)通过put和getKVS操作来操纵。状态被持久化存储并且状态的更新被记录为日志。请注意状态模型采用的是版本化的KVS,具体实现可以使用实际的KVS存储,但是也可以用关系数据库系统或其他的解决方案。

更正式地说,状态s被建模为一个字典元素K -> (V X N),其中:

K是一个键集合
V是一个值集合
N是一个版本号的无穷有序集合。映射函数next: N -> N取N中的一个元素并返回下一个版本号。
V和N都包含一个特别的元素⊥ (空类型),是N的最小元素。初始是所有的键都映射为(⊥, ⊥),对于s(k)=(v,ver)我们通过s(k).value表示v,用s(k).version表示ver。

KVS操作被如下建模:
- put(k,v) for k ∈ K and v ∈ V, 取得区块链的状态s然后转换为s`,这样对于所有的k’!=k,s’(k)=(v,next(s(k).version)),s’(k’)=s(k’) (后面这个等式没有太明白)
- get(k)返回s(k)

状态由对等点维护,而不是order节点和客户端。

状态分区。KVS中的键可以从它们链码的名字识别,在这个意义上,只有一个特定链码的交易可以修改属于这个链码的键。原则上,任何链码都可以读取属于其他链码的键。为了支持跨链码交易,修改属于两个或更多个链码的状态作为一个V1后的功能点。

1.2.2 账本
账本提供一份可验证历史信息,记录所有在系统操作中发生的成功的状态改变(即有效交易)和未成功的对改变状态的尝试(即无效交易)。

账本由排序服务构建为一个完整排序的交易(有效的和无效的)区块的哈希链。哈希链强制要求账本中的区块是完全排序的并且每个区块内包含的交易是完全排序的。

账本存储在所有的对等点里,也可以选择保存在一部分排序者里。存在排序者节点内的账本我们叫排序者的账本(OedererLedger),而存在对等点的账本我们叫对等点账本(PeerLedger)。对等点账本与排序者账本不同之处在于对等点账本本地维护一个比特掩码来区分有效交易和无效交易(更多细节见XX部分)

对等点可以像XX节(V1后功能)阐述的那样修订对等点账本。排序者维护排序者账本用于容错和(对等点账本的)可用性并可以随时决定修订对等点账本,前提是排序服务的属性(见1.3.3)保持不变。

账本允许对等点重播所有交易的历史来重构状态。因此,1.2.1节谈到的状态是一个可选的数据结构。

1.3. 节点(Nodes)
节点是区块链的通讯实体。一个“节点”只是一个逻辑功能,这样多个不同类型的节点可以运行在一台物理服务器上。重要的是节点如何组成“信任域”和如何与控制它们的逻辑实体相关联。

有三种节点类型:
1. 客户端(client)或提交客户端(submitting-client):一个客户端向背书者提交一笔实际发生的交易调用,并将交易提案广播给排序服务。
2. 对等点(peer):确认交易和维护账本的状态、保存账本的副本(Sec,1.2)。除此之外,对等点有一个特别的背书者角色。
3. 排序服务节点(Ordering-service-node )或者排序者(orderer):运行保证送达(deliver)的通信服务,如原子广播或完全排序广播。

下面将详细解释节点的类型。

1.3.1. 客户端
一个客户端是一个代表最终用户的实体。它必须与一个对等点连接以实现同区块链的通信。客户端自己选择连接到任意的对等点。客户端创建然后调用交易。

详见第2节,客户端与对等点和排序服务都通信。

1.3.2. 对等点
一个对等点从排序服务接收以区块形式更新的有序状态,并且维护状态和账本。

对等点可以额外承担一个特殊的背书节点(endorsing peer)的角色,或者称为背书者(endorser)。背书节点的特殊功能发生在特定的链码上,在一笔交易确认前为这笔交易背书。每一个链码可以指定一个关联一组背书节点的背书策略。这个策略定义了一个有效交易背书的充要条件(一般是一组背书者的签名),在第2节和第3节会详述。对于安装新链码来部署交易的情况,(部署)背书策略被指定为系统链码的背书策略。

1.3.3. 排序服务节点(排序者)
排序者构建排序服务,即一个提供保证送达(deliver)的通信结构。排序服务可以由不同的方法实现:从一个中心化的服务排序(例如在部署和测试时)到针对不同的网络和节点故障模型的分布式协议。

排序服务为客户端和对等点提供一个共享的通信通道,和一个交易信息的广播服务。客户端连接到通道(channel)上,然后可以在通道内广播信息传送(deliver)到全部的对等点上。通道支持全部信息的原子传递,即消息通信是可靠(针对特定实现方法)的完全排序(total-order)传递。换种说法,通道输出相同的信息到所有连通的对等点,并且按照相同的逻辑顺序输出。这个原子通信保证也称为完全顺序广(total-order broadcast),原子广播,或分布式系统上下文中的共识。发送的消息是将要包含在区块链状态中的候选交易。

分区(排序服务通道)。排序服务可以支持多通道,这个通道就类似于发布/订阅消息系统里的主题(topic)。客户端可以连接到一个给定的通道发送消息和接收消息。通道可以被理解为分区——连接到一个通道的客户不知道其他通道的存在,不过客户端可以连接到多个通道。即便Hyperledger Fabric的一些排序服务支持多通道,为了简化表达,本文档后续阐述将假定排序服务由一个单通组成。

排序服务API。对等点通过排序服务提供的接口连接到排序服务提供的通道上,排序服务API由两个基本操作组成(通常为异步事件):

TODO添加用于获取客户端或对等点指定序号的特定区块的API。

broadcast(blob):广播,客户端调用这个操作在通道内广播任意信息blob。在拜占庭容错问题中向一个服务发送一个请求的时候也被称为request(blob)。
deliver(seqno, prevhash, blob):传递,排序服务在对等点调用这个操作传递包含指定的非负整数序列号(seqno)和前一个传递的blob(prevhash)的哈希值的信息blob。(此处原文的格式比较清晰)换种说法,它是排序服务的一个输出事件。deliver()在发布-订阅系统中有时也被称为notify(),在拜占庭容错系统中被称为commit()。
个人理解:类比比特币区块链,广播就是将交易向全网广播,传递像是将交易顺序打包成区块。

账本和区块编队。账本(另见1.2.2节)包括排序服务输出的全部数据。简言之,它是一个deliver(seqno, prevhash, blob)的序列,依据计算前面提到的prevhash
来构建一条哈希链。

大多数时候,为了提高效率,排序服务会将交易(blob)打包,在一个deliver事件中输出多个区块,而不是每一笔交易单独输出。在这种情况下,排序服务必须对每一个区块里的交易强加一个确定的顺序。一个区块里的交易数量可以由一个排序服务的具体实现动态选择。

接下来,为了表达简便,我们基于每次deliver一个blob的假设定义了排序服务属性并且解释了交易背书(第2节)的工作流。这个概念可以轻松扩展到区块,基于上面说的一个block里面的blob有确定的顺序,我们假定一个block的deliver事件对应一个block里的每一个blob的一系列deliver事件。

排序服务属性
排序服务(或原子广播通道)的保证规定了广播消息会发生什么和完成传递的消息之间存在什么关系。这些保证如下:

安全性(持续保证):只要对等点连接上通道足够长的时间(他们可以断开连接或者宕机,但是会重启和重连),他们会看到唯一一串完成传递的(seqno, prevhash, blob)的信息。这意味着输出(deliver()事件)在所有对等点以相同的顺序发生,并且输出(output)依据序号为同一个序号携带相同的内容(blob和prehash)。注意这仅仅是一个逻辑顺序,而且一个对等点上的delever(seqno, prevhash, blob)不要求与在其他对等点上输出相同消息的delever(seqno, prevhash, blob)实时发生。换句话说,给定一个特定的seqno,没有两个对等点传递不同的prevhash或blob值。此外,除非某个客户端(对等点)实际调用了broadcast(blob),没有blob值会被传送,每个广播过的blob只传递一次。

此外,deliver()事件包含了前一个deliver()事件(prevhash)的加密哈希值。当排序服务实施原子广播保证时,prevhash是序列号为seqno-1的deliver()事件的哈希值。这样建立了一条跨deliver()事件的哈希链用于校验排序服务输出的正确性,后面的4、5节还会谈到。作为特例的第一个deliver()事件,prevhash有一个默认值。

活跃度(传递保证):排序服务的活跃度保证由一个具体的排序服务实现来指定。精确地保证依赖于网络和节点故障模型。
原则上,如果正在提交的客户端没发生故障,排序服务应该保证连接到排序服务的每个正确的对等点最终传递每一个提交的交易。

总的来说,排序服务保证如下属性
- Agreement。对于在正确的对等点的任意两个seqno相同的事件::deliver(seqno, prevhash0, blob0) 和 deliver(seqno, prevhash1, blob1),prevhash0==prevhash1 and blob0==blob1;
- 哈希链的完整性。对于正确对等点的任意两个事件:deliver(seqno-1, prevhash0, blob0) 和 deliver(seqno, prevhash, blob), prevhash = HASH(seqno-1||prevhash0||blob0).
- 无跳跃。如果一个排序服务在一个正确的对等点p输出
deliver(seqno, prevhash, blob),这个seqno>0,那么p已经传递过事件deliver(seqno-1, prevhash0, blob0);
- 无创建。正确的对等点的任何事件deliver(seqno, prevhash, blob) 必须被某些对等点(可能不同)的broadcast(blob)事件处理。
- 无复制(可选,但是可取)。对于任意两个事件broadcast(blob) 和 broadcast(blob’), 当两个事件deliver(seqno0, prevhash0, blob) and deliver(seqno1, prevhash1, blob’)在对等点发生并且blob == blob’,那么seqno0==seqno1 并且 prevhash0==prevhash1
- 活跃
如果一个正确的客户端调用了broadcast(blob)事件,那么每一个正确的对等点最终发出一个deliver(, , blob)事件,*代表任意值。

  1. 交易背书的基本工作流
    下面我们概述一笔交易的高层请求流程。

备注:注意下面的协议不假定所有的交易都被确认,它允许未确认的交易。

2.1. 客户端创建一笔交易发送给它选择的背书节点

客户端向它选定的一组背书节点发送一个PROPOSE消息(可能不是同时发送,见2.1.2和2.3节)来调用一笔交易。这一组给定chaincodeID的背书节点通过对等点对客户端提供服务,对等点通过背书策略认同一组背书节点。(见3节)。举个例子,交易可以被发送到给定chaincodeID的所有背书者。也就是说,某些背书者可以是离线的,其他背书者可以拒绝为这笔交易背书。提交的客户端尝试满足所有背书者的策略要求。

接下来,我们先详细介绍PROPOSE信息的格式然后讨论客户端和背书者之间可能的交互方式。

2.1.1. PROPOSE信息格式

PROPOSE信息的格式是

2.1.2 信息模式
客户端决定与背书者交互的顺序。比如,客户端一般发送

2.2. 背书节点模拟交易执行并生成背书签名
从客户端收到一个

2.3. 提交客户端为一笔交易收集一套背书然后通过排序服务广播给其他对等点
提交客户端等待收集“足够”的信息以及对(TRANSACTION-ENDORSED, tid, , )声明的签名以得出交易提案被成功背书的结论。如2.1.2节所讨论的,这可能涉及一轮或多轮与背书者的交互。

“足够”的精确数字取决于链码的背书策略(另见3节)。如果满足了背书策略,交易就完成背书;注意并不是确认。从背书节点收集到的签过名的TRANSACTION-ENDORSED信息被称为背书endorsement。

如果提交客户端不想为一个交易提案收集背书,它可以丢弃这笔交易,并可以选择稍后重试。

对于得到有效背书的交易,我们现在开始用到排序服务。提交客户端使用broadcast(blob)来调用排序服务,其中blob=endorsement。如果客户端没有直接调用排序服务的能力,它可以通过它选择的对等点来代理广播。客户端必须信任对等点不会删减任何背书信息,否则交易被认定为无效。注意,无论如何,一个代理对等点不可以捏造一份有效的背书。

2.4. 排序服务向对等点传送一笔交易
当一个事件deliver(seqno, prevhash, blob)发生并且一个对等点已经更新了序号小于seqno的blobs的状态,一个对等点会做如下事情:

它通过链码(blob.tran-proposal.chaincodeID) 的策略检查blob.endorsement是有效的。
一般情况下,他同时验证没有违反依赖关系(blob.endorsement.tran-proposal.readset) 。在更复杂的用例中,背书中的tran-proposal字段可以不同,这时背书策略(3节)指定状态如何改变。

依赖关系的验证可以通过不同的方法实现,这依据为状态更新选择的一致性属性或“隔离保证”。序列化是一种默认的隔离保证,除非链码的背书策略指定了其他方式。可序列化性可以由要求读集中的每一个键的版本等于这个键在状态中的版本来提供,并且拒绝不满足这个要求的交易。

如果所有这些检查都通过了,交易被认定为有效的或者确认的。此时,对等点标记PeerLedger中这笔交易的比特掩码为1,将blob.endorsement.tran-proposal.writeset应用于区块链状态(仅当tran-proposals相同时,否则背书策略逻辑定义了函数使用blob.endorsement)。
如果背书策略认证blob.endorsement失败了,交易是无效的,对等点标记PeerLedger的比特掩码为0.重要的是无效交易不改变状态。
请注意,这足以让所有(正确的)对等点在处理完传递给定序号的事件(区块)后达到相同的状态。亦即,借助排序服务的保证,所有正确的对等点会收到一串相同顺序地deliver(seqno, prevhash, blob)事件。因为对于背书策略的评估和读集中版本依赖关系的评估都是确定的,所有正确的对等点也会得出对于一个blob里的一笔交易是否有效的相同的结论。因此,所有的对等点承认和应用相同的交易序列,所有的对等点用相同的方式更新状态。