原版参见 Nightshade.pdf
原著:
Alex Skidanov Twitter: /AlexSkidanov Email: [email protected]
Illia Polosukhin Twitter: /ilblackdragon Email: [email protected]
翻译:Marco
校对:Crypto Rose
编辑:Buster Xu
术语表
术语 | 释义 | 备注 |
---|---|---|
beacon chain | 信标链 | |
block producer | 出块人 | |
chunk | 段 | 也称 片块 |
chunk producer | 出段人 | |
commit / reveal | 承诺 / 展示 | |
epoch | 周期 | |
erasure codes | 纠删码 | |
Ethereum Serenity | 以太坊宁静 | |
finalize | 确定 | |
fisherman | 渔夫 | |
Nightshade | 夜影 | |
part | 颗粒 | 经纠删码处理过的基本信息单位 |
partition | 分区 | |
receipt | 收据 | |
shard | 分片 | |
validator | 验证人 |
众所周知,以太坊作为迄今为止最广为使用的通用区块链,主网每秒只能处理不到20笔交易。这个局限性,加上该网络的流行度,导致了高额的gas价格(gas是网络上执行交易的开销)和漫长的确认时间。尽管在撰写该文的时候,以太坊平均每10到20秒产出一个新块,然而根据ETH Gas监测站的数据,一笔交易实际上要花1.2分钟才能加到链上。低产量,高价格和高延迟,这些都导致以太坊不适合运行那些要大规模使用的服务。
以太坊低产量的主要原因是,网络中的每个节点都要处理全网的每一笔交易。开发者们提出了许多解决方案,试图在协议层面解决这个问题。这些方案主要分为两类:一类是把所有的计算工作交给数量有限的强力节点;另一类是让网络中的每个节点只做所有计算工作的一部分。前一种方案的例子是Solana,系统中的每个节点通过细致的低层优化和GPU的使用,可以支撑每秒几十万笔简单的支付交易。Algorand,SpaceMesh,Thunder都归入这一类,他们通过改进共识以及区块结构本身来超过以太坊的TPS,但是仍然受制于单台机器(尽管非常强大)的处理能力。
后一种方案,把工作分给所有的参与节点,叫做分片。这也是当前以太坊基金会打算扩展以太坊的方式。撰写本文时,以太坊的分片规范还未定稿,最新的规范可以通过该链接查看。
Near协议也是基于分片的。Near团队的成员包括:若干前MemSQL工程师,负责构建分片、跨分片交易以及分布式的JOIN连接;5位前谷歌员工,他们在构建分布式系统方面有着丰富的行业经验。
本文重点阐述了区块链分片的通用方法,以及需要解决的主要问题,包括状态有效性和数据可用性问题。本文还提出了夜影方案,Near协议基于该方案进行打造,解决了上述问题。
让我们从最简单的分片方法开始。在这个方法中,不同于运行一个链,我们运行多个链,每个链称为一个“分片”。每个分片有自己的验证人集合。这里以及下文,我们都使用这个原生词汇“验证人”,指代那些进行交易验证和生产区块的参与者,不管是POW中挖矿的方式,还是投票机制。现在,让我们先假设分片之间互相不通信。
这种设计,尽管简单,却足够显现分片中初始面临的一些主要挑战了。
假设系统包含10个分片。第一个挑战是,由于每个分片有自己独立的验证人,现在每个分片的安全性都只有原先整条链的十分之一了。如果一个包含X位的非分片链决定硬分叉成一个分片的链,X位验证人就会分布到10个分片上,每个分片现在仅有 X/10 位验证人。攻击一个分片只需要买通5.1%(51% / 10)的验证人(见图1),
当今几乎所有的分片系统都依赖某种随机源来给分片分配验证人。区块链上的随机性本身就是一个极富挑战性的话题,它超出了本文讨论的范畴。现在,让我们假设有这样一个可用的随机源。我们会在2.1节介绍验证人分配的更多细节。
随机性和验证人分配都需要独立于任何特定分片的计算量。对于这种计算,几乎所有现在的设计都使用一个独立的区块链,执行维护整个网络所需要的操作。除了随机数生成和给分片分配验证人外,这些操作通常还包括接收分片的更新以及执行分片的快照,处理POS系统中的stake和slashing,以及在支持平衡功能的分片系统上进行分片的再平衡。这种独立链的名称,以太坊上叫信标链,波卡上叫中继链,Cosmos上叫Cosmos Hub。
本文统一将其称为信标链。信标链的存在引出了下面一个有趣的主题,平方分片。
分片经常被视为一种可以随着网络中节点数的增加而无限扩展的方案。实际上只在理论上有可能设计出这种方案,任何使用信标链概念的方案都不可能无限扩展。为了理解其原因,要意识到,信标链要做一些记账的计算工作,例如给分片分配验证人,或快照分片链区块,这些都与系统中的分片数量成比例关系。既然信标链本身只是一条单独的链,计算量受限于运行节点的算力,因此分片数量自然就受到了限制。
然而,分片网络的结构确实对任意节点的改进产生了倍增的效果。想一下这样一个场景,网络中节点效率作出任意的改进,使得节点处理交易的时间缩短。
如果这些改进了的节点运作着网络,包括信标链中的节点,都提高了4倍的速度。那么,每个分片都可以处理原先交易量的四倍多,而信标链可以维持原先分片数量的四倍多。整个系统产量的提升结果就是:4 × 4 = 16 倍,这就是平方分片名称的由来。
当前,对于究竟能支持多少分片,很难提供精确的度量。但是,在可以预见的未来,区块链用户的交易量需求不太可能超过平方分片的能力。在这么大量的分片中要安全运行如此多的节点数目,可能比当今所有区块链节点数的和还要高出一个数量级。
直到这里,我们还未准确定义当网络切割成分片后,究竟什么被分割开,什么没有被分割出去。特别的,区块链中的节点会执行三个重要任务:他们不仅要1)执行交易,还2)把经过验证的交易和构建完成的区块中继给其他节点,并且3)存储整个网络账本的状态和历史。三个任务中的每一个都对运行网络的节点提出了不断增长的需求:
上面的列表可能让人觉得,存储需求是最急迫的,因为它是唯一一个随着时间持续增长的因素(即使每秒交易量不变)。但实际上,当前最急迫的需求是算力。撰写本文时,以太坊的全网状态大小是100GB,大部分的节点都可以轻松应对。而以太坊的每秒可处理交易量在20笔左右,比许多实际应用场景的需求低了好几个数量级。
Zilliqa是最广为人知的对处理而不是存储进行分片的项目。对处理的分片相对容易:因为每个节点都有完整的状态,意味着合约可以自由调用其他合约,从链上读取任何数据。为了确保多个分片对状态同一部分的更新不会产生冲突,还需要一些细致的技术工作。从这方面来说,Zilliqa采用了相对简略的实现。
尽管之前有提出过分片存储而非分片处理的方案,但它是极不寻常的情况。在实践中,对存储的分片,也就是状态分片,几乎总是隐含了对处理的分片和网络的分片。
实际上,状态分片中,每个分片中的节点都在构建他们自己分片的区块链,这条链仅包含影响到属于这个分片的状态的那些交易。因此,分片的验证人只需要存储全局状态的本地(分片)部分,也仅仅执行和中继那些影响到这些本地状态的交易。这种分区线性减少了所有包括算力、存储、带宽的需求。但是这也带来了新的问题,例如数据可用性和跨分片交易的问题。这两个问题我们会在下文阐述。
迄今为止,我们描述的分片模型还不是很有用。因为,如果单个分片不能跟其他分片互相通信,整个系统不过就是多个独立的区块链而已。甚至分片技术还不可用的今天,不同区块链之间的互通依然是一个巨大的需求。
现在,让我们只考虑简单支付交易,每个参与者仅在一个分片上拥有账户。如果有用户希望在同一分片内从一个账户转账到另一账户,这笔交易可以由本分片的验证人全权处理。然而,如果分片1上的Alice想转账给分片2上的Bob,无论是分片1上的验证人(他们无法把账存入Bob的账户),还是分片2上的验证人(他们无法把账从Alice的账户取出),都不能处理整笔交易。
有两种跨分片交易的实现方案:
同步: 无论何时,一笔跨分片交易需要执行时,所涉及分片上含有相关交易状态转移信息的区块都同时生成。这些分片的验证人合作执行此类交易。
异步: 影响多个分片的跨分片交易在其所涉及的分片上异步执行。贷记方的分片一旦有了充足的证据,表明借记方分片已经执行了借记那边的工作,就可以执行自己这边的贷记工作。这种方案更趋于流行,因为它比较简单,并且易于协同。这种系统应用于当今的Cosmos、以太坊宁静、Near、Kadena等其他项目中。这种方案有一个问题,如果区块是独立产生的,相关区块中的某个成为孤儿块的可能性总归存在,导致整个交易仅被部分执行。想一下图2中描述的情况:两个分片都遭遇分叉。同时,一笔跨分片交易相应记录于块 A 和块 X’ 中。如果链 A-B 和 链 V’-X’-Y’-Z’ 成为了各自分片的正统链,则跨分片交易完全确认。若 A’-B’-C’-D’ 和 V-X 成为正统链,则跨分片交易完全被废弃,这也是可以接受的。但如果 A-B 和 V-X 成为正统链,则跨分片交易的一部分确认,另一部分被废弃,这会导致一个原子性错误。我们在第二部分中提出的协议里,会介绍该问题的解决办法。这部分会介绍我们就分片协议提出的分叉选择规则以及共识算法的变动。
构建这样一个系统,包含一个公共的信标链,和一系列虽然拥有不同属性、但有一系列足够类似的共识和区块结构的链,由此打造一个有可用互操作子系统的异构区块链生态系统。这样的系统不太可能有验证人轮转的功能,所以需要采取一些额外措施确保安全性。Cosmos和波卡实际上都属于这种系统。
这一节,我们将回顾,如果恶意验证人想破坏一个分片,会采用什么样的敌对行为。我们将在2.1节回顾那些防止分片破坏的经典实现方法。
一部分恶意验证人也许试图创建一个分叉。需要注意的是,无论底层共识是否是BFT,买通足够数量的验证人就都可能创建一个分叉。
相对来说,最可能发生的是买通一个分片中的50%以上(的验证人),而不是买通整个链的50%以上(的验证人)(我们将在2.1节进一步阐述这些可能性)。正如1.4节中所讨论的,跨分片交易牵涉到多个分片中的状态转换,那些分片中相应应用这些状态变化的区块要么全部确定(也就是说,出现在对应分片被选中的链分支上),要么全部成为孤儿块(也就是说,不出现在对应分片被选中的链分支上)。因为一般意义上,分片被破坏的可能性是不可忽视的,即使分片验证人之间已经达成了某种拜占庭共识或是包含状态变化的块之上已经产出许多区块,我们也不能假设不会发生分叉。
这个问题有多种解决方案,最常用的一种是,将分片链上最新的区块间或交叉连接到信标链上。分片链上的分叉选择规则相应变化为总是优选那些交叉连接的链,并且只对最后一个交叉连接之后产生的区块使用分片特定的分叉选择规则。
一些验证人也许会尝试创建一个区块,里面应用了不正确的状态转换函数。例如,从Alice有10枚代币、Bob有0枚代币的状态开始,区块可能包含包含一笔Alice转给Bob10枚代币的交易,但是最终状态如图3所示,Alice有0枚代币,Bob却获得了1000枚。
图4中有5个验证人,其中三个是作恶者。他们创建了一个无效区块 A’ ,并继续在其上创建新的区块。两个诚实验证人丢弃无效的 A’ ,并在最后一个在他们看来有效的区块上构建区块,导致了一个分叉。因为诚实验证人的分叉中验证人更少,所以他们的链更短一些。然而,在经典的非分片区块链中,每个出于各种目的使用区块链的参与者都负责验证所收到的所有区块,并重新计算状态。因此,任何关注该区块链的人都能发现 A’ 是无效的,并立即丢弃 B’ 、 C’ 和 D’ 。这样一来,链 A-B 就是当前最长的有效链了。
需要注意的是,与分叉不同,交叉连接到信标链并不是该问题的一个充分的解决方案,因为信标链无法验证所有区块。它只能验证该区块获得了分片中足够数量的验证人签名(并以此作为其正确性的证据)。
我们将在下面的2.2节讨论该问题的解决方案。
分片区块链的核心思想是大部分操作和使用网络的参与者无法验证所有分片的区块。因此,任何时候,参与者需要与某个特定的分片互动时,他们一般都无法下载和验证该分片的完整历史状态。
然而,分片的分区属性,引起了一个显著的潜在问题:如果不下载和验证特定分片的完整历史状态,参与者无法确信他们操作的状态是某个有效区块序列的结果,而且也不能确信这个序列就是该分片的正统链。这个问题在非分片区块链上是不存在的。
首先,我们会呈现解决该问题的一个简单方案,许多协议中也提出过这个方案。然后分析如何破解该方案,以及为了防止破解进行了哪些尝试。
解决状态有效性一个比较直接的方案如图5所示:假设整个系统有数千位验证人,其中不超过20%的是作恶者或会失效(比如掉线无法出块)。然后,如果我们取出200个验证人作为样本,那么样本中超过1/3的验证人会失效的概率在实际中可以视为0。
在这种诚实验证人百分比的假设下,若某分片的当前验证人集合提供了某个块,直接的方案就认为该块是有效的,且构建在这些验证人开始验证工作时就认定的该分片的正统链上。这些验证人从之前的验证人集合中识别了正统链,而根据同样的假设,这些验证人构建的区块就在正统链的前面。这样推导下去,整个链都是有效的。而且,既然任何时间节点的验证人集合都没有产生分叉,这种直接的方案也确信当前链就是该分片的唯一链。观察图6可获得一个直观感受。
恶意分叉可以通过向信标链交叉连接区块的方式解决。信标链通常在设计上就比分片链有明显更高的安全性。然而,创建无效区块显然是个解决起来更有挑战性的问题。
看一下图7的情况:分片1被攻破了,恶意行为人产生了无效的区块B。假设在这个区块B里,Alice的账户上凭空铸造了1000枚代币。恶意行为人接着在B后面创建了有效区块C(意思是C中的交易正确执行),掩盖了无效的区块B,并发起了一个到分片2的跨分片交易,内容是转账那1000枚代币给Bob的账户。此时,非法生成的代币反而驻留在分片2中一个完全有效的区块里。
解决该问题的一个可行的方法是:把分片排列成无向图,每个分片都与若干其他分片相连。并且只允许这些相邻分片之间的跨分片交易(例子有,这就是Vlad Zamfir的分片精要的做法,而且在Kadena的Chainweb中也用了相似的思想[1])。如果非相邻的分片需要一个跨分片交易,多个(相邻)分片路由的方式可以解决。在这个设计中,每个分片中的验证人都要验证自己分片和相邻分片的所有区块。下图有10个分片,每个都有4个相邻分片,对于跨分片交易的两个分片之间最多仅需要两跳(见图8)。
尽管只攻破单个分片不再是可行的攻击方法,攻破若干分片仍然存在问题。在图9中,一个对手同时攻破了分片1和2,使用来自无效区块B的资金,执行了到分片3的跨分片交易:
正确解决状态有效性问题有两个主要方向:渔夫与密码学计算证明。
第一种实现方法背后的思想是:一旦一个区块头因为某种目的(比如交叉连接到信标链或跨分片交易)在链间传递,就有一个时间窗口让诚实的验证人提交那个区块无效的证明。存在各种各样的设计,允许通过简洁的证明指认无效区块。对于接收节点来说,区块头的通信远比要接收整个区块小多了。
通过这种方法,只要分片中至少有一个诚实验证人,系统就是安全的。
处理后面这个问题的方法,参见3.7.2节。
第二种应对多分片贿赂的方案是用某种密码学的措施,允许一个人证明某个计算(比如从一系列交易中算出区块)被正确执行了。这种措施确实存在,比如 zk-SNARKs、zk-STARKs 等技术, 一些技术经常用在当今私人支付领域的区块链协议中,其中最知名的是ZCash。那些原语的主要问题是计算起来非常慢。例如,Coda协议,专门使用zk-SNARKs证明链上的所有区块都是有效的,在一次访谈中表示,为了创建一个证明,每笔交易要花费30秒时间(目前这个数字可能要小些)。
有趣的是,证明未必由一个可信实体进行计算。因为证明不仅能证实它的目标计算的有效性,还能证实其自身的有效性。因此,这种证明的计算工作可以分割给一系列参与者,与必须执行某些无需信任的计算相比,显著降低了冗余。它也允许计算zk-SNARKs的参与者运行在特殊的硬件上,同时不会减低系统的去中心化程度。
除了性能外,zk-SNARKs面临的挑战还有:
我们遇到的第二个问题是数据可用性。一般来说,运行特定区块链的节点分为两种:全节点,指那些下载每一个完整区块并验证每一笔交易的节点;和轻节点,指那些仅下载区块头、并对其关注的部分状态和交易应用默克尔证明的节点。参见图11。
这里有3个区块:前置区块,A,由诚实验证人创建;当前区块,B,由受贿的验证人签发;以及接下来的,C,也将由诚实验证人创建(区块链描绘在图的右下角)。
你是个商人。当前区块B的验证人从前代验证人手里接收到区块A,接着计算出你收到钱的区块,并发送给你一个包括默克尔证明的该块的头部,显示你拥有这笔钱的状态(或者是发送给你钱的有效交易的默克尔证明)。你确信这笔交易已确定,所以提供了相应服务。
然而,验证人从未把区块B的全部内容分发给任何人。因此,构建区块C的诚实验证人无法获取该区块,只能强制终止整个系统,或者在区块A上构建新块,同时剥夺你对那笔钱的所有权。
当我们把相似的场景放到分片上,全节点和轻节点的定义一般可以对应到每个分片上:每个分片的验证人下载该分片的每个区块并验证其中的每笔交易,但是系统中的其他节点,包括那些快照分片链状态到信标链的节点,都只下载头部。因此,分片验证人对该分片来说,实际上就是全节点,而系统的其他参与者,包括信标链,就像一个轻节点那样运作。
为了让我们前面讨论的渔夫方案有效,诚实验证人需要能下载交叉连接到信标链的区块。然而,如果恶意验证人交叉连接一个无效区块的头部(或使用它发起跨分片交易),但从不分发该区块,那么诚实验证人就无法发起挑战。
我们将介绍应对该问题的三种互补的解决方案。
亟待解决的最直接问题是,区块一旦被发布是否就是可用的。一种想法是,使用被称为公证人(Notary)的角色,让其在分片中以比验证人更快的频率轮转。他们的工作仅仅是下载一个区块并证明他们有能力下载它这个事实。他们之所以能更频繁的轮转,是因为无需下载分片的整个状态,不像验证人,后者每次轮转都要下载分片的状态,因此不能频繁轮转,如图13所示。
当一个特定的轻节点收到一个区块的hash时,为了提升节点对区块可用性的信息,它可以尝试下载该区块的一段随机碎片。这还不是一个完整的方案,因为除非轻节点的下载合起来能形成完整区块,恶意出块人可以选择扣押区块中未被任何轻节点下载的那部分内容,因此仍可以使区块不可用。
一个解决方案是使用一种称为纠删码的措施,即使只有区块的部分内容可用的情况下,也能恢复整个区块,如图14所示。
和大部分分片方案一样,波卡每个分片(叫做平行链)快照它的区块到信标链(叫做中继链)。假设中继链有2f+1个验证人。平行链的出块人叫做收集人,平行链区块一产出,收集人便计算该区块的一个纠删码版本,其中包含2f+1个部分,因此任意f个部分都能重建整个区块。然后他们给每个中继链的验证人分发一个部分。一个特定的中继链验证人,只有在中继链区块包含的每个快照平行链区块都收到其纠删码部分的情况下,才对该中继链区块签名。因此,如果一个中继链区块包含来自2f+1个验证人的签名,且只要其中不超过f个验证人违反了协议,那每个平行链区块都还是能从那些遵循了协议的验证人手中重建出来。见图15。
值得注意的是,上述方案都只能证明一件事:已经发布一个区块且当前是可用的。一段时间后,由于以下各种原因,区块可能变为不可用:节点下线,节点故意删除了历史数据等。
对于该问题,Polyshard [3]的白皮书值得一提:使用纠删码确保区块在分片之间是可用的,即使一些分片完全丢失了他们的数据也无妨。不幸的是,他们的特定实现需要任一分片都下载其他所有分片的区块,成本过于高昂。
还好长期可用性问题并不太急迫,因为系统并不指望其参与者能验证所有分片上的所有链。所以,分片协议的安全性设计需要能够做到:即使一些分片的旧区块变得完全不可用,整个系统依然是安全的。
这种多个分片链加一个信标链的分片模型非常强大,但也相当复杂。特别的,分叉选择规则需要在每个链上独立执行,而且分片链和信标链的分叉选择规则一定要有区别的构建和独立的测试。
夜影中,我们把系统建模成一个单独的区块链,其中每个块逻辑上包含所有分片的全部交易,以及改变所有分片的整个状态。然而,物理上,任何参与者都不会下载完整的状态或完整的逻辑区块。网络的每个参与者其实只是维护他们为之验证交易的分片上对应的状态。区块中的所有交易的列表都被分割成物理上的段,每个分片一个段。
在理想的条件下,每个(逻辑)块里,每个分片的每个块都正好有一个分段对应。这基本上对应到分片链模型中,分片链和信标链以相同的速度出块。然而,由于网络延迟,一些分段可能错过(出块),所以实践中,每个(逻辑)块的每个分片对应着0个或1个分段。对如何出块的详细介绍参见3.3节。
当今区块链共识的两个主流实现是:最长(或最重)链共识,指由最多工作量或权益打造的链被认为是正统链;以及BFT共识,指对于每个块,都由一批验证人在其中达成一个BFT共识。
在最近提出的协议中,后一种共识的实现更加流行一些。因为它提供了即时的确定性,而在最长链规则中,需要更多的区块创建在目标区块之后,才能确保确定性。通常,为了达到一个严谨的安全等级,需要小时级别的时间才能有足够数量的区块(在目标区块之后)创建出来。
对每个块使用BFT共识,也有些缺点,例如:
在一个混合模型中,共识使用的是某种最重链规则,但是周期性的对一些块使用BFT确定性工具。这样就同时保有了两种共识模型的优点。这种BFT确定性工具有用于以太坊2.0的Casper FFG [6]、Casper CBC(参见 https://vitalik.ca/general/2018/12/05/cbc_casper.html) 和用于波卡的GRANDPA (参见 https:// medium.com/polkadot-network/d08a24a021b5)。
夜影使用最重链共识。特别的,当一个出块人产生一个区块时(参见3.3节),他们可以从其他出块人和验证人中收集签名,作为对前一个区块的证明。参见3.8节对于如何积累如此大量签名的详细介绍。一个区块的权重就是所有那些在区块中签了名的签名者积累的抵押权益。而一个链的权重就是区块权重的和。
在最重链共识之上,我们使用一个确定性工具,利用证明确定区块。为了减低系统复杂度,我们使用的确定性工具在任何情况下都不影响分叉选择规则,而只是引入额外的惩罚条件。这样一来,一旦一个区块被确定性工具确定下来,就不可能出现分叉,除非占总权益极大百分比的权益都被罚没了。Casper CBC就是这样一种确定性工具,我们目前意向使用它建模。
我们也在研究一个独立的BFT协议,叫做TxFlow。撰写本文时,尚不清楚TxFlow是否会用来取代Casper CBC。然而,我们注意到,确定性工具的选择在很大程度上与设计的其余部分无关。
夜影中有两种角色:出块人和验证人。设系统有w个出块人,以及wv个验证人,在我们的模型里w=100,v=100,wv=10000。作为一个权益证明(POS)系统,意味着出块人和验证人把一定数量的内部货币(称为“代币”)锁定一段时间,且这段锁定期远超过他们用来执行构建和验证链的时间。
与所有的POS系统一样,不一定w个出块人和wv个验证人都是独立的实体,因为这是无法强制的。然而,w个出块人和wv个验证人中的每一个人都单独抵押权益。
系统包含n个分片,我们的模型中n=1000。正如3.1节提到的,夜影中没有分片链,取而代之的是,所有的出块人和验证人共同构建单一的区块链,我们称之为主链。主链的状态被分到n个分片中。每个出块人和验证人在任何时候都只在在本地下载对应某个分片子集的状态子集,且只处理和验证影响这部分状态的交易。
要成为出块人,一个网络参与者锁定一大笔代币(一笔质押权益)。网络按周期进行维护,一个周期是数天级别的时间长度。每个周期之初,质押权益最多的前w个参与者就是该周期的出块人。每个出块人指派给Sw个分片,(设Sw=40,则每分片有 SwW/n = 4个出块人)。在周期开始前,出块人下载所指派分片的状态,然后在整个周期中收集影响该分片的交易,并把它们应用到状态上。
对于主链上的每个块b,和每个分片s,都有一个指定给s的出块人负责b上有关该分片的部分。这个部分被称为段,包含了b中有关该分片的交易列表,以及结果状态的默克尔树根。b最终只包含该段的一个很小的段头,也就是所有应用交易的默克尔树根(具体信息参见3.7.1节),和最终状态的默克尔树根。
本文剩余部分,我们通常把在特定时间为特定分片创建段的出块人称为出段人,出段人一般是出块人之一。
出块人和出段人根据一个固定的计划轮换每个块。出块人有个顺序并依序重复出块。例如,如果有100个出块人,第一位出块人负责创建块1、101、201等,第二位出块人负责2、102、202等。
创建段与创建块不同,需要维护状态。而且对每个分片来说,仅仅 SwW/n 位出块人维护着每个分片的状态,相应的也就只有这SwW/n位出块人轮换创建段。例如,按照上面的常量设定,一个分片指定了4个出块人,每个出块人每四个块创建一次段。
为了确保数据可用性,我们使用了跟2.5.3节描述的波卡类似的方式。一旦一个出块人创建了一个段,就创建该段的一个纠删码版本,纠删码参数为(w, floor(w/6 + 1)) (译者注:即将该段分成w个片段,只要收集到floor(w/6+1)个片段即可重建该段)。
然后发送该段纠删码的每一片(我们称这样的码片为段颗粒chunk parts,或者就叫颗粒parts)给每个出块人。
我们计算出一颗默克尔树,其中每个颗粒都是叶子,并在每个段的头部放入该默克尔树的根。
这些颗粒通过单颗粒消息(onepart message)发给验证人。每条这种消息包含该段的段头、颗粒序号以及颗粒内容。消息中还包含创建该段的出块人的签名,以及证明该颗粒对应头部且由正确的出块人创建的默克尔路径。
一旦一个出块人收到一个主链的块,他首先检查自己是否有该块包含的每个段的单颗粒消息。如果没有,该块就不被处理,直到获取到缺失的单颗粒消息。
一旦所有的单颗粒消息收全了,这个出块人对自己持有状态的分片,通过对端获取其余的颗粒,并重建对应的那些段。
出现以下情况时,出块人不会处理主链区块:如果区块中至少有一个段的对应单颗粒消息未收到;或如果他负责维护状态的分片中至少有一个无法重建出对应的完整段。
若要一个特定的段可用,只要有 floor(w/6) + 1 的出块人持有并能提供相关的颗粒即可。因此,只要作恶者的数量不超过floor(w/3),就不会让一个超过半数出块人(在线)的链含有无效的段。(译者注:为了让段有效,最少要有floor(w/6)+1个颗粒,因此在超过半数出块人在线时,其中作恶者的数量不能超过 w/2 + 1 - (w/6 + 1) = w/3 )。
如果一个出块人遇到一个缺失了一条单颗粒消息的块,他可能仍然会签名。因为如果这个块最终上链了,就能使该出块人的利益最大化。而且这对出块人没有风险,因为过后无法证明这个出块人没有那条单颗粒消息。
为了解决这个问题,我们让每个出段人在创建段的时候为后面经过编码的段的每个颗粒选择一个颜色(红或绿),并在编码前将所选颜色的掩码存入段中。每条单颗粒消息都包含该颗粒指定的颜色,这个颜色也用来计算编码颗粒的默克尔根。如果出段人偏离了协议,将被轻易检测出来。因为,要么默克尔根对不上单颗粒消息,要么匹配了默克尔根的单颗粒消息中的颜色与段的掩码不同。
当出块人签名一个区块时,他将收到的该块中所有段的红色颗粒做成掩码放在签名中。发布一个不正确的掩码是一种可惩罚行为。如果一个出块人没有收到某条单颗粒消息,是无法知道其颜色的,因此尝试盲签区块就有50%的可能性会被惩罚。
出段人创建段时,只是选择哪些交易要放在段中,而不会执行状态转换。相应的,段头部包含的默克尔根是执行该段交易之前的默克尔状态。
仅当一个包括该段的完整区块得到处理时,这些交易才会执行。而一个参与者仅在以下条件达成时处理区块:
一旦区块得到处理,对于那些需要维护状态的每个分片,参与者执行交易并计算交易执行后的新状态。之后,如果被分配到任何分片,参与者就准备好创建下个区块的对应段,因为他已经有了新默克尔状态的默克尔根。
如果一笔交易不止影响一个分片,就需要在各自的分片中连续独立执行。完整的交易发给第一个相关的分片。一旦该分片的段中包含了该交易,且包含了该段的块被执行,就会产生一笔称为收据(receipt)的交易。该收据被路由给下一个需要执行该交易的分片。如果跨分片交易需要更多的步骤,则当执行收据交易的时候,会产生一笔新的收据交易,如此继续下去。
理想的情况是,收据交易在产生收据的块的下一个块中立刻被执行。只有在维护源分片的出块人接收并执行了前一个块后,收据交易才会生成。并且,需要在目的分片的出块人为下一个区块创建段之前被其知晓。因此,在这两个事件间的一段短时间内,收据必须由源分片通信到达目的分片。
假设 A 是最后产生的区块,包含交易 t ,而 t 产生了收据 r 。把下一个将产生的区块记为 B (也就是说,这个块将 A 作为其前置区块),我们希望在 B 中包含 r 。假设 t 在分片 a 中,而 r 在分片 b 中。
如图18中所述,收据的生命周期如下:
产生和存储收据。 分片 a 的出段人 cp(a) 收到块 A , 执行交易 t ,并生成收据 r 。 cp(a) 接着将所有生成的收据存储在它内置的持久化存储中,以源分片的id为索引。
分发收据。 一旦 cp(a) 准备为块 B 创建分片 a 的段,他获取到所有通过执行块 A 中分片 a 的相关交易时产生的收据,并将它们包含在块 B 对应分片 a 的段中。段生成后, cp(a) 产生它的纠删码版本,和所有对应的单颗粒消息。 cp(a) 知道哪些出块人维护哪些分片的全状态。对一个特定的出块人 bp , cp(a) 在发布区块 B 里对应分片 a 的段时,把一些收据放进单颗粒消息中,这些收据是执行块 A 中有关分片 a 的交易的结果,且这些交易的目的分片是 bp 去维护状态的(收据放进单颗粒消息的展示参见图17)。
接收收据。 回忆一下,参与者(包括出块人和验证人)不会执行块,除非他们有了块中每个分片的单颗粒消息。因此,在任何参与者执行区块 B 的时候,他们已经获得 B 中所有段对应的全部单颗粒消息,也就拥有了所有传入的收据,这些收据以参与者维护状态的分片为目的地。当执行一个特定分片的状态转换时,参与者既执行单颗粒消息中与该分片有关的收据交易,也执行该段自身包含的所有交易。
有可能指向一个特定分片的收据的数量过大,超过了其处理能力。例如,图19的情况,每个分片的交易都产生了一个指向分片1的收据。到下个区块时,分片1需要处理的收据数量已经与上个区块中所有分片合起来要处理的一样多了。
需要注意的是,如果这种负载不均衡持续了一大段时间,则收据从创建到执行之间的延迟可能持续低无限增长。一种解决方式是,当收据指向的是处理延迟超过了某个常数(例如,一个周期)的分片时,该收据产生的交易会被丢弃。
考虑图20的情况。直到区块B时,分片4还没有处理完所有收据。它仅处理到来自区块A中分片3所产生的收据,并记录了下来。在区块C时,收据处理跟进到区块B的分片5,最后在区块D时,该分片赶上了进度,处理了区块B里所有遗留的收据和区块C里的全部收据。
一个特定分片的段(或在分片链模型中一个特定分片链的块)只能由那些维护其状态的参与者验证。他们可以是出块人、验证人或仅仅是那些在分片中存有资产的外部的见证人,见证人也会下载和验证分片状态。
本文中,我们假设大部分验证人不会存储绝大部分分片的状态。然而值得提到的是,确实有一些分片链,是基于这样的假设设计的:大部分参与者都有能力存储和验证大部分分片的状态,例如QuarkChain。
因为仅有一部分参与者拥有能够验证分片的段的状态,这就有可能对那些有状态的参与者进行自适应攻陷,执行一个无效的状态转换。
在多个分片设计方案中,每隔几天就对验证人采样。一天内,分片链中的任何块,只要获得了超过2/3的验证人对该分片的签名,就立刻视为确定的块。通过这种方法,一个采用自适应攻陷的对手只需贿赂一个分片链中 2n/3 + 1的验证人,就能执行一个无效的状态转换。尽管这种攻击很难实现,但对于一条公链来说,这种等级的安全性还不够。
正如2.3节所讨论的,常见的方案是在出块后留出一个时间窗口,让任何持有状态的参与者(无论是出块人、验证人还是外部观察者)挑战其有效性。这类参与者被称为渔夫。为了让渔夫能够挑战一个无效区块,必须确保区块对渔夫来说是可用的。夜影中数据可用性的讨论在3.4节。
夜影中,一个块产生后,(其中的)段除了实际的出段人,没人可以验证。特别是,发布区块的出块人自然不持有大部分分片的状态,并且无法验证段。当下一个区块创建时,会包含多个出块人和验证人的证词(参见3.2节),但既然大部分出块人和验证人都不持有大部分分片的状态,所以仅包含一个无效段的块也能收集到超过半数的证词,并继续存在于最重的链上。
为了解决该问题,我们允许任何持有分片状态的参与者,对该分片产生的任何一个无效段,提交一个线上挑战。
一旦参与者检测到一个特定的段是无效的,他们需要提供一个证实该段无效的证明。因为大部分网络参与者并不维护无效段所在分片的状态,证明中需要包含足够的信息保证无需持有状态即可确认该块无效。
我们设了一个门限 Ls ,代表单一交易所能累积读取或写入的状态数量上限(以字节为单位)。任何访问超过 Ls 个状态量的交易都被视为无效。3.5节中说过,特定区块 B 中的段只包括将被执行的交易,不包括新的状态根。区块 B 中该段包括的状态根是执行这些交易之前的状态根,是执行了区块 B 之前区块中同一个分片的最后一个段中交易的结果。一个作恶人想要执行一个无效状态转换,就要把一个错误的状态根放到区块 B 中,这个状态根与执行前段中交易的结果状态并不对应。
我们把出段人放在段中的信息扩展一下。不再放置执行所有交易之后的状态,而是将交易分割成相邻集合,每个集合都总计读写了 Ls 字节的状态。然后逐个集合的执行这些交易,存储这些连续交易集合的状态。有了这些信息,渔夫就可以创建一个错误状态转换的挑战,找到第一个无效的状态根,在上一个带有默克尔证明的状态根(有效的)和当前带有默克尔证明的状态根之间发生的交易所影响到的 Ls 字节状态放入。这样,任何参与者都可以验证这批交易,确认该段是无效的。
类似的,如果出段人试图把一个读写超过 Ls 字节状态的交易包含进来,对挑战来说,只要包括带有默克尔证明的、该交易访问的前 Ls 个字节即可。这已足够执行交易并证明在某个时刻,有过尝试读写超过 Ls 字节内容的情况发生。
正如2.3节中讨论的,一旦我们假设分片的段(在分片链模型中的分片区块)可能无效,并引入了挑战时间,就会对确定性以及跨分片通信有负面影响。特别的,任何跨分片交易的目的分片无法确认源分片的段或块是否确定了,直到挑战时间结束(参见图21)。
解决它的办法是,用某种方式让跨分片交易立即完成:对目的分片,不等原分片交易发布后挑战结束,立即执行收据交易。如果后面发现源段或块无效,就目的分片和源分片一起回滚(参见图22)。对于夜影的设计来说,应用该方案很自然。因为分片链不是独立的,而是由分片的段一起,发布在同一个主链区块中。如果发现任何段是无效的,包含这个段的整个块、以及构建在其上的块都被视为无效。参见图23。
假设整个挑战过程足够长,上面的方案就都具有原子性。我们采用后一种方案,因为在普通情况下提供快速的跨分片交易,相比目的分片回滚带来的不便,前者更重要。这种因某个源分片上无效的状态转换所导致的目的分片回滚,是极度罕见的事件。
挑战机制的存在已经显著降低了自适应攻陷的可能性。因为确定一个含有无效状态转换的段并通过挑战时间,采用自适应攻陷的对手需要贿赂所有维护该分片状态的参与者,包括所有(该分片的)验证人。
预测发生这种事情的可能性是极端复杂的,因为当今活动的分片链都没有经历足够长的时间,让人尝试这种攻击。我们认为这种风险,尽管可能性很低,但对于一个预期要执行数百万笔交易和执行世界级金融操作的系统来说,还是足够高了。
这个判断来自两个主要原因:
通过隐藏验证人分配到分片的信息,我们进一步减少了自适应攻陷的可能性。这个思想与Algorand [5] 隐藏验证人的方法有些类似。
有一点需要重要指出的是,即使是Algorand或下文描述的方法,把验证人隐藏了,自适应攻陷还是存在理论上的可能性。尽管自适应攻陷的对手不知道参与者会创建或验证一个区块或段,参与者自己却是知道他们将要做的任务,并有一个相关的密码学证明。因此,对手可以广播他们贿赂的意图,并支付给愿意提供那个密码学证明的参与者。但是,我们注意到,因为对手不知道他们将要贿赂的分片上的验证人,除了在整个社区里广播他们贿赂特定分片的意图外,别无他法。这是让任何诚实参与者都可以开启全节点去验证那个分片来获得经济利益的时机。因为在那个分片上出现无效区块的几率变大了,这就是创建挑战、获得相关奖励的机会。
为了不暴露特定分片的验证人,做如下处理(参见图24):
使用VRF进行分派。 每个周期开始时,每个验证人使用VRF得到指定分片的掩码。每个验证人的掩码都将含有Sw个比特位(参见3.3节对Sw的定义)(译者注:此处应是指掩码中有Sw位是1,代表每个验证人同时验证Sw个分片)。接下来,验证人获取相关分片的状态,并在整个周期内,对收到的每个块验证其中指派给自己的分片所对应的段。
对块签名而不是段。 既然分片指派是隐藏的,验证人就不能在段上签名,而是通常对整个块签名,这样就不会暴露他验证哪些分片。特别的,当一个验证人收到一个区块并验证了所有的段后,他要么创建一条消息,证明他负责的所有分片(没有以任何方式表明是哪些分片)中所有的段都是有效的;要么在任意一个段无效时,创建一条消息,包含一个无效状态转换的证明。对如何聚合这些消息的详细介绍,参见3.8节。对如何防止验证人照抄其他验证人消息的详细介绍,参见3.7.4节。以及3.7.5节,详细介绍了当一个成功的无效状态转换挑战发生时,如何奖惩验证人。
验证人的一个常见问题是,他可以跳过状态的下载和对段和区块的实际验证,而是观察网络,看其他验证人提交的内容并重复他们的消息。采用这种策略的验证人并不能给网络带来任何安全的提升,只是获得奖励。
一个解决该问题的常见方案是,要求每个验证人提供一个证明,证实他们确实验证了区块。例如,提供一个执行状态转换的唯独痕迹,但是这种证明大大增加了验证的开销。
如上所述,一旦验证人收到一个含有无效段的区块,他们先准备一个无效状态转换的证明(参见3.7.1节),然后提交这样一个证明(参见3.7.4节),再在一段时间后展示这个挑战。一旦块里包含一个被展示的挑战,接下来会:
为了让一个拥有上千分片的系统安全运行,我们需要有上万级或更多的验证人。如3.7节所述,我们想要每个验证人在每个块都承诺某条消息并签名。即使承诺的消息是一样的,聚合出一个BLS签名并对其进行验证都是极度昂贵的。更不用说承诺和展示的消息在验证人之间通常是不同的,所以我们需要某种别的方法来聚合这些消息和签名,以支持后面对它的快速验证。
我们使用的方法如下:
验证人关联出块人。 因为周期开始前要留出下载状态的时间,所以在周期开始前的某个时刻,出块人就确定了。而且与验证人不同,出块人不是隐藏的。每个出块人有v个验证人槽位。验证人提交链下请求给出块人,让自己成为其v个(关联)验证人之一。如果出块人愿意关联该验证人,就提交一笔交易,包括最初来自验证人的链下请求和出块人同意其加入的签名。要注意,验证人所验证的分片无需和其关联的出块人负责出段的分片一致。如果一个验证人多次申请加入多个出块人,只有来自第一个出块人的交易会成功。
出块人收集提交信息。 出块人始终收集来自验证人承诺和展示的消息。只要积累了一定数量的这些消息,出块人就计算这些消息的默克尔树,给每个验证人发送默克尔根和各自的默克尔路径。验证人验证路径并在默克尔根上签名。接着,出块人就在验证人给的默克尔根(译者注:含有验证人签名)上累积一个BLS签名,然后仅仅发布默克尔根和累积签名。出块人也使用廉价ECDSA签名方式,签名保证前面那个多签的有效性。如果多签与提交的默克尔根不匹配,或者与验证人的掩码不匹配,就是需要惩罚的行为。同步链时,参与者可以选择验证来自于验证人的所有BLS签名(这个开销将极度昂贵,因为牵涉到聚合验证人的公钥),或仅验证出块人的ECDMA签名,并且该出块人没有被挑战和惩罚。
对挑战使用链上交易和默克尔证明。 你会注意到,没有检测到无效状态转换时,展示验证人的消息是没有价值的。只有那些包含无效状态转换实际证明的消息需要展示,也仅仅只有这些消息需要表现出自己与之前的承诺相匹配。展示消息有两个目的:
对于这两条,我们需要解决两个问题:
后一种机制仅允许用来证明无效状态转换。这种情况极其罕见,因此不会导致区块中的垃圾交易问题。
最后一个要解决的问题是,出块人可以选择不参与消息聚合或故意审查特定的验证人。我们通过出块人的奖励与关联的验证人数量挂钩,使那种行为在经济上不可取。我们也注意到,因为周期之间的出块人有很大的交集(因为总是权益抵押最多的前w个参与者入选),验证人可以很大程度上坚持与相同的出块人关联,也就降低了关联到一个会审查他们的出块人的风险。
因为主链上出块非常频繁,下载全历史可能很快就变得昂贵。而且,因为每个块包含一个大批验证人的BLS签名,仅仅是聚合公钥来检查签名的开销就会变得过高。
最后,因为在可以预见的未来,以太坊1.0很可能依旧是最常用的区块链之一,需要一条重要的渠道能从Near往以太坊转移资产。而当今,通过验证BLS签名来确保Near区块的有效性在以太坊那一侧是行不通的。
夜影主链上的每个块可选择是否包含一个Schnorr多签,内容是上一个包含Schnorr多签的区块的头部。我们称这样的块为快照(snapshot)块。每个周期的头一个块必定是快照块。当处理这样一个多签时,出块人也必须积累上一个快照块上验证人的BLS签名,并以3.8节描述的方法聚合他们。
既然整个周期中,出块人集合都是不变的,仅验证每个周期的第一个快照块就足够了。前提是没有发生大比例的出块人和验证人被贿赂和出现一个分叉的情况。
周期的第一个块必须带有足以计算本周期出块人和验证人的信息。
我们把仅包含快照块的主链的子链,称为一条快照链。
创建Schnorr多签是个互动的过程, 但既然我们仅需要低频的执行它,那么不管多低效,任何过程都可以满足。Schnorr多签可以在以太坊上方面得到轻松验证,为安全的跨链通信提供了重要的原语。
为了同步Near链,只需要下载所有的快照块,并确认Schnorr 多签是正确的(也可以选择验证个别验证人BLS签名),然后仅仅从最后一个快照块开始同步主链上的块。
这篇文章中,我们讨论了构建分片区块链的方式,以及两个主要挑战和现存的应对方案,分别是状态有效性和数据可用性。然后我们提出了夜影,一个助力NEAR协议的分片设计。
设计工作还在推进中,如果你有相关意见、问题或反馈,请前往 https://near.chat 。
[1] Monica Quaintance Will Martino and Stuart Popejoy. Chainweb: A proofof-work parallel-chain architecture for massive throughput. 2018.
[2] Mustafa Al-Bassam, Alberto Sonnino, and Vitalik Buterin. Fraud proofs: Maximising light client security and scaling blockchains with dishonest majorities. CoRR, abs/1809.09044, 2018.
[3] Songze Li, Mingchao Yu, Salman Avestimehr, Sreeram Kannan, and Pramod Viswanath. Polyshard: Coded sharding achieves linearly scaling efficiency and security simultaneously. CoRR, abs/1809.10361, 2018.
[4] Ittai Abraham, Guy Gueta, and Dahlia Malkhi. Hot-stuff the linear, optimalresilience, one-message BFT devil. CoRR, abs/1803.05069, 2018.
[5] Yossi Gilad, Rotem Hemo, Silvio Micali, Georgios Vlachos, and Nickolai Zeldovich. Algorand: Scaling byzantine agreements for cryptocurrencies. In Proceedings of the 26th Symposium on Operating Systems Principles, SOSP ’17, pages 51–68, New York, NY, USA, 2017. ACM.
[6] Vitalik Buterin and Virgil Griffith. Casper the friendly finality gadget. CoRR, abs/1710.09437, 2017.