对官方的原文进行了简单的翻译:
arch-deep-dive
感觉原文有一点点啰嗦(更可能是本人英文有点搓),翻译的更不到位,后面根据理解再整理个简单点的
总览
v1架构有如下优势:
- chaincode信任的灵活性 架构将chaincode(区块链应用)的信任假设和ordering的信任假设进行了分离。也即是说,ordering服务可能由一组节点(orderer)提供服务,容忍其中部分节点失效或作恶,而每个chaincode的背书都有可能不同。
- 可扩展性 由于执行chaincode的背书节点(endorser)与orderers节点是正交的,系统的扩展能力比将相关功能集成在同一个节点要好多的。特别的,当不同的chaincode指定了不相交的背书,其将引入endorsers之间chaincode的分区并允许chaincode并行执行(背书)。此外,chaincode执行一般成本是比较高的,这样就从ordering服务的关键路径上移除了(注:保证ordering服务的响应)
译注:官方文档,一而再,再而三的提到将orderer节点拆分出来是多么英明。只是记得,流水化操作、其提升扩展性、并发性都是常识。关键是要能识别出什么操作可以拆分出来,也就是哪些操作是正交的、并且确实有必要拆分的,这是设计中的难点。
- 保密性 该架构有助于chaincode的部署,其有内容和自身交易状态更新的保密需求。
- 共识模块化 架构是模块化的,共识(如ordering服务)是可插拔的
本文分为两部分:
I.Hyperledger Fabric v1 架构组件
1. 系统架构
2. 交易背书的基本流程
3. 背书策略
II. post-v1架构组件
4. 帐本checkpoint(修剪)
1. 系统架构
区块链是包含很多相互通信的节点组成的分布式系统。区块链执行的程序称为chaincode,其拥有状态、帐本数据、执行交易。chaincode是核心,因为交易是在chaincode上调用的操作。交易必须被“背书”,只有被背书的交易可以被提交,然后影响状态。存在一个或多个为管理功能和参数的特别chaincode,统称为系统chaincode。
1.1 交易
交易分为两种:
- 发布交易 - 创建新的chaincode,携带程序作为参数。当一个发布交易成功执行,该chaincode就已经成功安装在区块链上。
- 执行交易 - 执行之前发布的chaincode内容中的操作。一个执行交易指一个chaincode和其提供的功能。当前chaincode执行成功,其可能涉及到相关状态的修改和输出
正如后续描述的,发布交易是执行交易的特例,当一个发布交易创建新的chaincode,对应一个系统chaincode的执行交易。
备注: 本文当前假设一个交易不论是创建新的chaincode还是执行一个操作,都是由一个已经发布的chaincode提供。本文不涉及到:a) 为只读检索优化的交易(包含在v1版本),b)支持跨chaincode的交易(post-v1 特性)
1.2 区块链数据结构
1.2.1 状态
区块链的最新状态(简单说状态)的模型是带版本的键值存储(k/v存储),其中key是名字、值则是任意。这些条目运行在区块链上的chaincode通过get
和put
KVS操作修改的。状态存储是持久的,状态的更新会记录日志。注意,带版本的kv存储是适配状态模型的,其实现可能使用实际的kv存储,不过RDBM存储或其它方案也是可行的。
更正式的,状态 s
的模型为 K -> (V X N)
映射。其中:
-
K
是一组key -
V
是一组value -
N
是有序的无穷版本号。映射函数:next: N -> N
需要一个N
元素,然后返回一个新的版本号。
V
和 N
包含特别的元素:\bot
,这是最低的N
元素。任何key的初始化都映射为(\bot, \bot)
。对s(k)=(v,ver)
我们表示 v
为 s(k).value
, ver
为s(k).version
kv存储操作模型如下:
-
put(k,v)
, 其中k\in K
、v\in V
, 将区块链状态s
改变为s'
, 因此s'(k)=(v,next(s(k).version))
withs'(k')=s(k')
for allk'!=k
-
get(k)
返回s(k)
状态有peer节点维护,而不是order或客户端
状态分区 KV存储中的key可以根据名字识别其属于哪个chaincode,在这个意义上,只有特定chaincode的交易可以修改属于该chaincode的key。原理上,任何chaincode可以读取属于其它chaincode的key。跨chaincode交易的支持,也就是修改属于2个及以上chaincode状态是post-v1的特性。
1.2.2 帐本
帐本提供可验证的历史记录,包括发生在系统操作期间的所有成功的状态修改(有效的交易)和未成功的尝试(无效交易)。
帐本由ordering服务构建,是完全有序交易块的hash链。hash链强制区块必须完全顺序排列在帐本中,每个块包含一个完全排序的交易队列。这种强制排序是涉及所有交易的。
帐本保存在所有peer节点,也可以作为orderer的子集。一个orderer指向的帐本称为OrdererLedger
,而peer指向的称为PeerLedger
。PeerLedger
不同于OrdererLedger
的是,peer 本地维护了一个bitmask来区分有效和无效的交易。
peer可能对 PeerLedger
进行减枝(post-v1特性)。orderer维护OrdererLedger
是为了容错和可用,其可能在任何时候对其减枝,只要维护ordering服务的提供。
帐本允许peer对所有历史交易进行重访来重构状态。因此,1.2.1章节描述的状态是一个可选数据结构。
译注:比特币的钱包余额就是如此通过所有相关交易计算出来的,而比特币本事是没有余额的。
1.3 节点
节点是区块链的通信实体。“节点”只是逻辑上的概念,多种不同类型的节点可能运行在同一个物理服务器上。重要的是,节点是如何被分组到“信任域”,如何关联到控制它们的逻辑实体。
目前有三种类型节点:
- 客户端 或 提交客户端:客户端提交一个实际的交易调用给代理人(endorser),然后广播交易提案给ordering服务
- Peer : 提交交易、维护状态和一份帐本拷贝,此外,其还有一个特别的endorser角色
- Ordering-service-node 或 orderer:运行通信服务实现交付保证,如原子化或完全排序广播。
详细解释如下。
1.3.1 客户端
客户端表示终端用户操作的实体。其必须连接到peer来实现与区块链的通信。客户端可以连接到任何它选择的peer。客户端创建并调用交易。
如章节2描述的,客户端同时与peer和orderer节点通信。
1.3.2 peer
peer从ordering服务接收放在块中经过排序的状态更新,并维护状态和帐本。
peer此外可以作为背书peers,或者是endorser。背书节点的特殊功能在对应特定chaincode生效,在交易被提交前对其背书。每个chaincode都可能指定一个背书策略,其指向一组背书节点。策略定义了有效的交易背书所需的必要充分条件,在章节2、3内描述。在部署chaincode的特例中,安装新chaincode的背书策略是指定的系统chaincode的背书策略。
1.3.3 ordering服务节点(orderer)
orderers节点组成 ordering服务,也就是提供交付保证的通信结构。 ordering服务可以用采用不同的实现方式:从中心化服务(比如使用在开发和测试中)到支持不同网络和节点故障模型的分布式协议。
ordering服务提供共享的通信通道给客户端和peer节点,提供广播服务用语携带交易的消息。客户端连接到通道,然后可能在通道内广播消息提交给所有peer节点。通道支持所有消息的原子提交,也就是说,消息痛惜是全局排序发布和可靠的(依赖具体实现)。换句话说,通道输出相同的消息给所有连接的peer节点,并且是相同的逻辑顺序。此原子通信保证在分布式系统上下文中也被称为total-order broadcast, 原子广播、或共识。通信消息被包含在区块链状态中作为候选交易。
分区(ordering服务通道) Ordering服务支持多个channel,channel类似于 主题发布/订阅(pub/sub)消息系统。客户端连接到特定的channel,然后发送消息,获取送达的消息。channel是分区的,客户端连接到一个channel而不知道其它channel的存在,当然客户端可以连接到多个channel。即使一些ordering服务的实现包括在Hyperledger Fabric v1中会支持多通道,但为了简单起见,在本文其它部分都假设ordering服务仅包含单个channel/主题。
ordering服务API peer通过接口连接到ordering服务的提供channel。ordering服务API包含2个基本操作(异步事件):
-
广播(blob)
: 客户端调用该接口通过channel广播任意消息blob
。发送请求给服务时,BFT上下文中也为称为request(blob)
。 -
发布(seqno,prehash,blob)
: ordering服务在peer节点调用该接口来发布消息blob
,带有非负整数序列号(seqno
)和最近发送的blob的hash值(prehash
)。也就是说,这是从ordering服务输出的事件。deliver()
在发布-订阅系统中也被称为notify()
或者是BFT中的commit()
帐本和块信息 帐本(参加 1.2.2)包含ordering服务的所有输出数据。简单来说,它是deliver(seqno, prehash, blob)
事件的序列,其根据前面描述的prehash
计算结果构成hash链。
大部分时候,为了效率考虑,ordering服务将多个blob批量在一个独立的deliver
时间中输出多个block,而不是仅输出单个交易(blob)。这种情况下,ordering服务必须保证每个块内的blob是明确排序的。一个block中blob的数目是根据ordering服务实现动态选择。
本文后面,为了便于描述,我们定义ordering服务属性和解释交易背书流程时假设每个deliver
事件包含一个blob
。这很容易扩展到多个块,假设一个块的devilver
事件对应于块内每个blob独立的deliver
序列,而根据上述描述,每个块内的blob都是确定排序的。
ordering服务属性
ordering服务保证(或者 原子广播channel)规定了广播消息时会发生什么和发布消息之间的关系是什么。这些保证如下:
1. 安全(一致性保证) 只要peer节点连接到channel的时间足够长,它们将看到相同的发布(seqno, prehash, blob)
消息序列。这意味着(delever()
事件)输出在所有peer节点上是相同顺序的,根据序列号,相同序列号有想吐的内容(blob
和prehash
)。注意,这仅仅是逻辑顺序,并不是两个节点实时的同时收到相同的消息。换个说法,一个给定的seqno
,没有两个正确的peer会发布不同的prehash
或blob
值。此外,没有值blob
会被发布,除非一些客户端(peer)调用了broadcast(blob)
,而且,每个广播blob仅发布一次。
deliver()
事件包含上一个devliver()
事件中数据的加密hash - prehash
。当ordering服务实现原子广播,prehash
是seqno-1
序列号deliver()
事件的加密hash。这构成了deliver()
事件的hash链,其用来验证ordering服务的输出,章节4、5会进一步讨论。第一个delier()
事件是个特例,其prehash
是默认值。(因为没有前一个事件啊)
2. Liveness(发布保证) ordering服务的Liveness保证是由ordering服务的具体实现确定的。实际的保证依赖于网络和节点故障模型。
原理上,如果提交客户端不故障,ordering服务将能保证每个连接到ordering服务的正常peer最终会发布每个提交的交易。
看到这里,有经验的开发人员应该都能明白大概使用的什么分布式算法了。
总结下,ordering服务有如下属性:
- 一致 任何两个事件在正常的节点上,有相同的序列号就有相同的内容;
- hashchain integrity 在任何正常节点上,后一个事件的prehash都是前一个事件的加密hash,
prehash=HASH(seqno-1||prevhash0||blob0)
- 不跳过 一个大于0的序列号事件被输出,其前一个序列号事件必然已经被发布
- 不创建 任何正常的peer节点的任何
deliver(seqno, prevhash, blob)
事件必然有前置的broadcast(blob)
事件 - 不重复(可选,非必需) 对任何两个
broadcast(blob)
事件,如果其blob相同,其deliver()
事件必然相同 - Liveness 如果客户端调用一个
broadcast(blob)
事件,每个正常的peer都最终(有限时间内)会产生一个deliver(*, *, blob)
事件。
2. 交易背书的基本流程
下面我们概要的描述下高纬度交易请求流程。
备注:注意下面的协议不假设所有交易是确定的,也就是说,它允许非确定的交易。
2.1 客户端创建交易并发送给其选择的背书节点
为了调用一个交易,客户端发送 PROPOSE
(提案) 消息给其选择的一组背书节点(可能不是同时 - 详见 2.1.2和2.3)。这组给定chaincodeID
的背书节点通过peer使对客户端可用??,反过来通过背书策略支持这组背书节点。比如,交易是发送给一个给定chaincodeID
的全部endorser。也就是说,一些endorser可能离线,其它一些可能会拒绝、选择不背书该交易。提交客户端的会尝试用可用的endorser来满足策略。
下面,我们第一次详细描述 PROPOSE
消息格式,然后讨论下提交客户端和endorser之间交互可能的模式。
2.1.1 PROPOSE
消息格式
PROPOSE
消息格式为
,其中:tx
是必须的,anchor
是可选的,解释如下:
-
tx=
, 其中:-
clientID
是提交客户端的ID -
chaincodeID
指向交易所属的 chaincode -
txPayload
是包含交易中的 payload -
timestamp
(时间戳)是由客户端维护的单调增正整数 -
clientSig
是客户端的对tx
其它字段的签名
txPayload
在执行交易和发布交易时不同的(执行交易指向一个发布用的系统chaincode)。对于执行交易,txPayload
将包含两个字段:-
txPayload =
,其中:-
operation
(操作)表示chaincode操作(函数)和参数 -
metadata
(元数据)表示与调用相关的属性
-
对于发布交易,
txPayload
则由三个字段组成:-
txPayload =
, 其中:-
source
(源码)表示 chaincode的源码 -
metadata
(元数据)表示chaincode和应用相关的属性 -
policies
包含所有peer可访问的chaincode相关的策略,如背书策略。注意:背书策略不是通过发布交易的txPayload
提供的,但是发布交易的txPayload
包含背书策略的ID和其参数。
-
-
anchor
包含读取版本依赖,或者更明确地,key版本对(anchor
是KxN
的子集),绑定或"锚定"PROPOSE
的请求到KV存储中特定版本的key。如果客户端指定了anchor
参数,endorser仅对其本地KV存储匹配的anchor
之上的读版本交易进行背书。
tx
的加密hash被所有的节点作为唯一的交易识别tid
(tid = HAHS(tx)
).客户端保存tid
在内存中,等待从背书节点返回的响应。
2.1.2 消息模式
客户端决定与endorser交互的顺序。比如,客户端一般发送
给单个endorser,然后其产生一个版本依赖anchor
, 客户端之后使用其作为PROPOSE
消息的参数发送给其它的endorser。又如,客户端直接发送
(没有anchor
)给其选择的所有endorser。通信模式的差异是可能的,客户端可以自由决定(参考2.3)
2.2 背书节点模拟交易并产生背书签名
在接收到从客户端发送的
消息后,背书节点epID
首先研制客户端签名clientSig
,然后模拟交易。如果客户端指定了 anchor
仅对其本地KV存储匹配的anchor
之上的读版本交易进行模拟。
模拟交易涉及到背书节点批准执行交易(txPayload
),通过执行chaincodeID
指向的chaincode,拷贝本地持有的状态。
作为执行的结果,背书节点计算读版本依赖(readset
-读集)和更新状态(writeset
- 写集),也在DB语言中也被称为 MVCC+postimage
回想一下,状态由键/值对(k/v)组成。所有 k/v 条目是有版本的,也就是说,每个条目包括排序的版本信息,每次key中存储的value更新时其值都会递增。解释交易执行的peer记录所有的k/v对都可以被chaincode访问,包括读取和写入,但是peer还没更新其状态。更具体的:
- 背书节点执行交易前的状态
s
,对每个被交易读取的keyk
,(k, s(k).version)
对被添加到readset
- 此外,对每个被交易修改的key
k
其新值为v'
,(k, v')
对被添加到writeset
。另外,v'
可以是新值到之前值(s(k).value
)的增量。
如果客户端在PROPSOE
消息中指定了anchor
,客户端指定的anchor
必须等于背书节点模拟交易产生的readset
。
然后,peer转发内部的tran-proposal
(可能还有tx
)到其背书交易的逻辑部分,简称背书逻辑。默认情况下,peer的背书逻辑接受tran-proposal
然后简单签名tran-proposal
。然而,背书逻辑可能解释任意功能,比如与legacy系统以tran-proposal
和tx
作为输入进行交互,以决定是否背书交易。
如果背书逻辑决定背书交易,它会发送
消息到提交客户端(tx.clientID
),其中:
-
tran-proposal := (epID, tid, chaincodeID, txContentBlob, readset, writeset)
, 其中txContentBlob
是 chaincode/交易指定的信息,将txContentBlob
作为tx
的某种表示(比如,txContentBlob = tx.txPayload
) -
epSig
是背书节点在tran-proposal
上的签名
如果背书逻辑拒绝背书交易,endorser可能发送endorser (TRANSACTION-INVALID, tid, REJECTED)
消息给提交客户端。
注意,endorser不会在这一步修改其状态,在背书上下文中的交易模拟产生的更新并不会影响状态!
2.3 提交客户端收集交易背书并通过ordering服务广播
提交客户端会等待接收到"足够"的信息后,签名(TRANSACTION-ENDORSED, tid, *, *)
以确认交易提案已经被背书。如在2.1.2中讨论的,这涉及到与 endorser的一轮或多轮交互。
"足够"的具体数目取决与chaincode的背书策略(见章节3)。如果背书策略得到满足,交易被背书,注意:其还没有被提交。背书节点建立交易的TRANSACTION-ENDORSED
消息的签名集合被赞同/背书称为 endorsement,表示为endorsement
如果提交客户端没有为交易提案收集到提案,它会放弃该交易,提供稍后重试的选项。
对有有效背书的交易,我们现在开始使用ordering服务。提交客户端执行ordering服务使用broadcast(blob)
,当blob=endorsement
。如果客户端不能直接调用ordering服务,它会通过一些它选择peer的做代理。这样的peer必须备客户端信任,不会从endorsement
中移除任何消息,否则交易可能被认为是无效的。注意,代理peer可能无法fabricate有效的endorsement
。
2.4 ordering 服务发布交易给peers
当deliver(seqno, prehash, blob)
事件发生,peer已经对对低于seqno
序列号的blobs状态都已经更新了。peer执行如下:
- 它检查
blob.endorsement
是否有效,根据chaincode(blob.tran-proposal.chaincodeID
)指向的策略。 - 在典型情况下,它将验证(
blob.endorsement.tran-proposal.readset
)没有违法依赖。在更复杂的用例中,endorsement 中trans-proposal
的字段可能不同,在这种情况下,背书策略(章节3)指定了状态如何演变的。
验证依赖可以采用不同的方式实现,根据其选择的一致性属性或"隔离保证"来做状态更新。串行化是默认的隔离保证,除非chaincode背书策略特别指定了不同的部分。通过readset
中每个key关联版本,等于状态中的key版本,来提供串行化,不符合需求的交易会被拒绝。
- 如果所有的检查通过,交易就被认为是有效或提交的。这种情况下,peer在
PeerLedger
中的bitmask中标记此交易为1(置1),应用blob.endorsement.tran-proposal.writeset
到区块链状态(如果tans-proposals
是像的,否则背书策略逻辑定义了blob.endorsement
的功能) - 如果
blob.endorsement
的背书策略验证失败,交易是无效,peer在PeerLedger
中的bitmask标记为0。特别注意, 无效交易不会改变状态。
注意,在处理带给定序列号的发布事件(块)后,所有(正确的)peers有相同的状态是充分的。也就是说,ordering服务的保证,所有正确的peers会接收到想吐的事件(deliver(seqno, prehash, blob)
)序列。由于背书策略的评估和readset
中的版本依赖的评估是确定的,所有正确的peers都将得出相同的结论,不论交易包含的blob是否有效。因此,所有peers提交和应用相同的交易序列,更新它们状态在相同的路径。
Figure 1. Illustration of one possible transaction flow (common-case path). 图1. 一个可能交易流程的示意图(常用路径)
3. 背书策略
3.1 背书策略指定
背书策略就是背书交易所需的条件。区块链peer还有一组预定义的背书策略,由安装chaincode的deploy
交易引用。背书策略可以有参数,这些参数通过deploy
交易指定。
为保证区块链和安全属性,这组背书策略必须由一组证明策略来限制一组功能以保证有限的执行时间(可终止)、确定性、性能和安全。
动态的添加背书策略(比如,通过在chaincode部署时间进行deploy
交易)是非常敏感的,为了限制策略评估时间(终止)、确定性、性能和安全。因此,动态添加背书策略是不被允许的,但在将来会被支持。
3.2 对背书策略的交易评估
交易只有根据策略被背书后才被认为是有效的。chaincode执行交易首先必须满足chaincode的策略以获取背书,否则就不会被提交。正如章节2解释的,这发生在提交客户端和背书节点之间交互过程中。
正式的背书策略是对背书的断言,后续状态可能是TRUE或FALSE。对于发布交易,背书是根据系统范围的策略获取背书的。
背书策略断言是指特定的变量。它可能是指:
- keys 或者 chaincode相关的ID(在chaincode的元数据中),比如,一组 endorsers
- chaincode更进一步的元数据
-
endorsement
和endorsement.tran-proposal
的元素 - 其它
上述列表是通过表现力和复杂度升序排列的,也就是说,支持仅涉及节点的密钥和身份的策略将比较简单。
背书策略的评估必须是确定的。背书必须被每个peer在本地评估,这样就不需要与其它peer进行交互,也就是所有正常的节点都应该采用相同的方式评估背书策略。
3.3 背书策略的例子
断言可能包含逻辑表达式,评估为 TRUE 或 FALSE。通常情况,由背书节点为chaincode发出的交易调用会使用数字签名。
假设chaincode指定链endorsor集合为 E = {Alice, Bob, Charlie, Dave, Eve, Frank, George}
, 示例策略:
- 从 E 中所有成员来的相同
tran-proposal
有有效的签名 - E中任何单个成员的有效签名
- 从背书节点来的相同
tran-proposal
是验证签名有效的条件是:(Alice OR Bob) AND (any two of: Charlie, Dave, Eve, Frank, George)
- 对相同的
tran-proposal
验证签名有效通过7个endorsers中任意5个。(更通用的,对有n > 3f
个endorsers的chaincode,通过n
中2f+1
个endorsers即可验证签名,或者是超过(n+f)/2
个endorsers) - 假设endorsers有"权重"或"赌注",比如
{Alice=49, Bob=15, Charlie=15, Dave=10, Eve=7, Frank=3, George=1}
,总的赌注是100: 策略需要大部分赌注来验证签名(比如合并赌注严格大于50),比如{Alice, X}
集合,其中X
是除了 George 之外任意一个,又或者{everyone together except Alice}
,等等都满足条件。 - 上面条件中的赌注分配可以是确定的(在chaincode的元数据中固定),也可以是动态的(比如,根据chaincode的状态,在执行期间进行调整)
- 在
tran-proposal1
上验证从(Alice OR Bob)
来的签名,验证在tran-proposal2
中(any two of: Charlie, Dave, Eve, Frank, George)
的签名,其中其中tran-proposal1
andtran-proposal2
仅在背书节点和状态更新是有差异。
这些策略的怎么用取决于应用,解决方案是要灵活一点还是要针对endorsers的失败或作恶,同时还有其它各种属性要考虑。
4. 验证帐本和PeerLedger
checkpoint(修剪)
4.1 验证帐本(VLedger)
为了维持帐本的抽象,仅包含有效和提交的交易(比如在比特币中),peer可能需要额外的状态的帐本来维护验证帐本(或 VLedger)。这是从帐本中过滤掉无效交易的hash链。
VLedger区块(称为vBlocks)的构造过程如下。由于PeerLedger
区块会包含无效的交易,这些交易可以在交易从区块添加到vBlock时被peer过滤掉。每个peer都独立的处理自己的部分。vBlock就是定义成没有无效交易的区块,无效的都被过滤掉了。因此vBlock的大小本质是动态的,可能会空的。构造vBlock的示意图如下所示:
vBlocks是由每个peer通过hash链串在一起的。更进一步,验证帐本包含的每个块包含:
- 上一个vBlock的hash
- vBlock的编号
- 从上一个vBlock开始被peers提交的有效交易,这些交易是排序列表形式组织
- 派生出该vBlock的区块(在
PeerLedger
中)的hash
checkpoint
所有这些信息由peer进行级联和计算hash,在验证帐本中生成vBlock的hash。
4.2 PeerLedger
checkpoint
帐本是包含无效交易的,其可能不需要永远记录。但是,peer不能简单的将PeerLedger
中的块丢掉,因此在建立vBlocks时修剪(整理)PeerLedger
. 也就是说,在这种情况下,如果一个新peer加入网络,其它peers不会将丢弃的块发给它,也不会说服新来的它们的vBlocks是有效的。
为了实现PeerLedger
的修剪,本文描述了一种checkpoint机制。这种机制建立在跨peer网络的vBlock的有效性,允许checkpointvBlocks替代丢弃的PeerLedger
区块。反过来说,因为不需要存储无效的交易,可以降低存储空间。同样可以降低新加入网络的peer重建状态的工作量(因为不需要在重放PeerLedger
来重建状态时再逐个交易进行验证,只需要简单的重放验证过的帐本的状态更新)。
4.2.1 checkpointing 协议
peer每CHK个区块执行一次checkpointing,CHK是可配置的参数。为了初始化checkpoint,peer广播(比如,gossip)给其它peers指向验证帐本的消息:
,其中
-
blockno
是当前块编号,blocknohash
是其对应的hash -
stateHash
是有效的blockno
区块的最新状态的hash(比如通过默克尔hash生成) -
peerSig
是peer对(CHECKPOINT, blocknohash,blockno,statehash)
的签名
peer会持续收集 CHECKPOINT
消息,直到其得到足够多的、经过正确签名的、blockno
与blocknohash
和stateHash
匹配的消息,然后建立有效的checkpoint(4.2.2)
在有blocknohas
的区块号blockno
之上建立有效的checkpoint:
- 如果
blockno>latestValidCheckpoint.blockno
,则peer设定latestValidCheckpoint=(blocknohash,blockno)
- 将构成有效checkpoint的相关peer签名保存在
latestValidCheckpointProof
集合 - 保存状态相关
stateHash
到latestValidCheckpointedState
- (可选) 修剪其
PeerLedger
到区块号为blockno
(含)的区块
4.2.2 验证checkpoint
很容易想到checkpointing会带来如下的问题:何时peer可以修剪其PeerLedger
? 多少个 CHECKPOINT
是"足够多"?
这些都定义在checkpoint验证策略中,至少有两种可能的方式,也可以组合实现:
- Local (peer-specific) checkpoint validity policy (LCVP) - 本地检查点验证策略 - 给定的peer p指定一组peer,这些peers是p信任的,它们的
CHECHPOINT
消息足够建立有效的checkpoint。比如,LCVP在peer Alice可能定义了其需要接受到Bob的CHECKPOINT
消息,或者同时从Charlie和Dave收到。 - Global checkpoint validity policy (GCVP) - 全局检查点验证策略 - 和本地策略很相似,除了它是在系统(区块链)层面定义,而不是peer本地。比如,GCVP可能会指定:
- 如果从11个不同的peers得到确认,peer就可以信任checkpoint
- 在特定的部署中,每个orderer都和一个peer部署在同一个机器上(比如,信任域),其支持最多有f个orderer故障(拜占庭算法),每个peer收到 f+1个不同peers(与orderer搭档的)的确认就可以信任该checkpoint