本文作者:星际联盟 原创作品,转载请注明出处
“时空证明”,是Filecoin内使用的让存储矿工提供自己在相应的时间内存储了用户的数据的证明的机制,以下是关于时空证明的方方面面。
选举PoSt
“选举PoSt”组合了选举PoSt和SurprisePoSt回调:
- 通过耦合“领导选举”和PoSt,“选举PoSt”保证了矿工为了挣取区块奖励,必须在每轮做些工作来证明他们的扇区。
- 小型矿工可能不能经常赢得奖励,因此出现了SurprisePoSt,其代表矿工必须进行PoSt的频率的下限,保证“算力表”不会因为那些处于“长尾”部分的小矿工而变得陈旧。
概览
“选举PoSt”耦合了PoSt过程和“区块产生”过程,意味着为了产出一个区块,矿工必须产出一个有效的PoSt证明。具体来说,矿工正在存储的无报错扇区的一个子集允许他们使用一个PartialTicket去尝试一次“领导选举”,且使用任一个PartialTicket都可导出一个有效的用于“领导选举”的ChallengeTicket。抓取一个获胜ChallengeTicket的可能性依赖于扇区大小和总存储量。矿工在每一个指定epoch内获得的奖励,与他们生成的获胜ChallengeTickets数量成正比。因此这也激励着矿工们在一个“领导选举”内,为了展现他们的完整算力而尽可能多地检查被允许的存储。矿工在一个给定epoch内能产生的“选举证明”数量将决定其赚取多少区块奖励。
通过对指定的PartialTicket进行哈希运算,并使用此哈希值生成一个[0,1]间的值来产生选举证明,即
ChallengeTicket = H(PartialTicket)/2^len(H)
这并不表示,在指定的某轮里,幸运矿工可以通过仅仅证明单个扇区而成功地生成一个区块。正常来说,为了能向区块的生成贡献所有力量,矿工将必须在每轮里证明他们的扇区。遇到矿工不能证明某个扇区的情况时(如矿工不能从扇区访问节点),就不会有任何的tickets返回。为了阻止这种情况的发生,矿工可以声明他们的出错扇区,从而避免不得不在合格扇区集合中包含它们的情况。他们的算力也会相应地减少。
“选举PoSt”的构造过程包含一个叫做SurprisePoSt的清理过程,后者将随机性地挑战在一段时间没有被选举的领导,以便他们可以证明自己的存储。在预期中,矿工们将在每一个证明周期内被挑战一次。任何没能使用SurprisePoSt成功回应挑战的矿工,将会看到他们遍布所有扇区的算力都受到抑制(这被称做一个DetectedFault)。如果不能在另外两个SurprisePoSts之内证明他们的算力的话,他们的算力将会被永久地终结。
选举PoSt
为了缩短PoSt回应时间,矿工们被选举出来挖掘区块时就得提交一次PoSt,因此叫做“选举Post”。矿工赢得一次区块打包权时,需要立即生成一个PoSt证明,并将之和获胜PartialTickets一起提交到区块头里。PartialTickets和PoSt证明都会在“区块验证”时,由“存储算力一致性子系统”进行检查。区块被包含时,一条特殊的消息被添加,此消息调用SubmitElectionPoSt,后者处理扇区更新的方式将会与成功的SubmitSurprisePoSt相同。
SuprisePoSt清理
正常情况下,若缺少已投递的选举PoSt,公链为了保证矿工的存储依然可靠,会在每个证明周期里随机挑战他们提交一次SurprisePoSt。此过程大部分和选举PoSt相同,但成功的SurprisePoSt不会给予矿工生产区块的资格,也不会给他们任何奖励,它只允许他们在算力表中维护算力信息。
对于每个被挑战的矿工,都会有一条NotifyOfPoStSurpriseChallenge作为链上消息公布并发送给选中的StorageMinerActor。那些没有赢得区块的小型矿工,和那些在向网络输送算力的矿工,会频繁地使用SurprisePoSt,因为他们没有能力赢得选举PoSt。
细节
生成“选举PoSt”
Filecoin的选举PoSt过程运用了两个“系统库调用”:
- GenerateCandidates:以矿工的扇区和期望的Challenge Tickets数值为参数,为大量随机选中的被挑战扇区生成包含证明,生成的数量与challenged tickets数值成正比。
- GeneratePoSt:代入一组ChallengeTickets并为它们生成一个时空证明,以整体地证明矿工的存储。
就像上面所描述的,系统鼓励矿工为了检查他们是否被选举为领导,在每个“区块时间”内重复这个过程。“选举PoSt”所做的理性假设是,持续存储文件并获得相应的奖励,将会比在不同的epoch内重新生成数据,偶发性地参与领导选举对矿工更有利。
通过对访问扇区内一系列被挑战节点的证明(如来自已提交扇区的默克尔树的叶子),矿工可以生成一组有效的ChallengeTickets,以将它们当作EC内领导选举的一部分进行检查(为了找出获胜的tickets)。获胜的tickets将被存储到区块上,并会被用于生成一个PoSt。因此区块头部将包含许多“获胜”的PoSt候选者(每个包含一个partialTicket, SectorID和其它元素,用于验证领导选举)和从ChallengeTickets生成的Post证明。
以上这些都会被包含在ElectionPoStOutput字段里。
为了简化实现和界定区块头的大小,系统会为每个区块设置可能的选举证明的最大数值。举例来说,对于EC就是E=5,我们可以将递交挑战ticket限制在每个区块16次,这就覆盖了超过50%的矿工遇到的99.99%以上的情况。
epoch时间被切分为三个不重叠的部分: 1.检查扇区,搜寻小的挑战tickets 2.计算SNARK,产出一个验证获胜挑战tickets的PoSt 3.区块产出和广播 每部分的目标时长仍有待调整。证明参数的调整可能会允许一个更长的区块广播窗口,同时维护着PoSt的安全性(但以密封时间和其它事物为代价)。
如果没人在一个epoch里找到获胜ticket,就将epoch做为post_randomness采样的一部分进行递增并重试。
在每一轮里: 1.随机性采样: 矿工在指定的epoch SPC和被用作post_randomness的epoch里,从一个随机链上拿取一个随机的ticket,并在后面拼接上“矿工ID”
post_randomness = VRF_miner(ChainRandomness(currentBlockHeight - SPC.post_lookback))
选择合格扇区
PoSt扇区被针对“证明集合”而不仅仅是活动扇区采样,因为如果PoSt成功了,“证明集合”内所有的PoSt都会变为活动的。也就是说,矿工通过在**“证明集合”**中包含这些扇区,声明这些是活动扇区,而这将在PoSt中得到证明。因此,我们可以使用这些扇区来声明PoSt。
numSectorsMiner = len(miner.provingSet) numSectorsSampled = ceil(EPoStSampleRate * numSectorsMiner)
注意即便 numSectorsSampled == len(ProvingSet),此过程在任何指定的epoch中也都可能不会对一个矿工的所有扇区进行采样,这取决于如何在挑战扇区内选择将要证明的数据。这中间可能会存在碰撞,比如一些扇区被多次选择。 1.对每一个选中的扇区生成Partial Ticket
- 首先,从选中扇区随机采样,生成K个PoStChallenges(C_i)。ChallengeRangeSize用以表示一个选中的挑战范围大小。挑战范围必须是subtree-aligned的,也因此会切分扇区为固定大小的数据块。因为允许的挑战范围不会重叠,所以挑战范围可以被索引。C_i对应于一个大小已知的数据块的特定索引。
C_i = HashToBlockNumber(post_randomness || S_j || i)
- 对于每一个C_i,矿工从磁盘读取特定范围的数据,大小为ChallengeRangeSize
- 矿工使用所有PoSt证人生成一个PartialTicket
- 计算PartialTicket:
PartialTicket = H(post_randomness || minerID ||S_ j || C_1_Output || … || C_K_Output)2.为获胜者检查Challenge Ticket 给定返回的PartialTickets,矿工在其上使用在Expected Consensus中定义的目标集合查找获胜tickets,TicketIsWinner()方法如下:
winningTickets = []
def checkTicketForWinners(partialTickets):
for partialTicket in partialTickets:
challengeTicket = finalizeTicket(PartialTicket)
if TicketIsWinner(challengeTicket):
winningTickets += partialTicket
def finalizeTicket(partialTicket):
return H(ChallengeTicket) / 2^len(H)
单个获胜ticket已可以用于提交一个区块,但为了提高可能得到的奖励,矿工应该想尽可能多地检查ticket。目标值保证了,考虑到ElectionPoStSampleRate,正常情况下,矿工如果检查了他们所有的tickets,他们的总算力就会被展现出来。
为区块头内所包含的生成一个PoSt证明
- 使用获胜ticket(同样的SetctorNumber可能会有多个ticket),调用来自proofs的GeneratePoSt为区块生成单个PoSt。
如果没人在本epoch里找到获胜ticket,就将epoch做为post_randomness采样的一部分进行递增并重试。
参数:
- Randomness_ticket – 从随机链上拿取的ticket。
- Randomness_lookback – 为了在随机链上取得随机性,需要回溯多远 - 将会有PoSt安全性所允许的那么大,可能是1或2个epoch
- K (e.g. 20-100s) - 每个扇区的挑战数目,为了保证PoSpace的安全,此值必须足够大。
- ChallengeRangeSize - 挑战所读取的大小(在32B和256KB之间),基于安全分析之上。
- EPoStsampleRate - 扇区采样因子 – 为了使全部重新生成扇区变得不合逻辑,这个值应该足够大。如果全部验证对磁盘有害,系统会截取一部分。
- sectorsSampled - 给定EPoStsampleRate下采样的扇区数。
- networkPower - Filecoin网络算力,读自算力表,单位字节。
报错
错误检测
错误检测贯穿着扇区的生命周期。当扇区因为某些原因不可用时,矿工为使此扇区在PoSt挑战开始前不会被测试,必须提交已知的faults。只有在challenge time时被报告的错误才是做数。如果出现任何其它错误,矿工提交本次证明周期的有效PoSt就很可能失败,此外还有:
- 不可在ChallengePeriod期间声明错误
- 不可在ChallengePeriod期间修复错误
相应地,如果矿工不向挑战做出回应,他们将丢失他们所有的算力和一部分抵押。这种情况被认为是一个DetectedFault,且证明集合中的所有扇区将被标记为Failing。矿工将在下一个证明周期内重新挑战。如果矿工连续MAX_CONSECUTIVE_FAULTS次都没有向挑战提供一个有效的回应,他们的存储交易抵押将相应地被削减(浏览 Deal States 以查看更多)。
Expected Consensus中规定任何出错的扇区将不算入矿工的算力。通过这些Detected和Declared的错误,算力表应该紧密地追踪网络中的算力。
错误恢复
为了从错误中恢复,再次让出错扇区活动起来,矿工必须标记错误为recovering状态,并接着提交一个证明恢复中扇区的PoSt。当成功提交这样的一个PoSt证明,所有的错误便被重置并假定已被恢复。矿工必须要么解决掉失败的扇区,并在下次证明递交时接收针对它的挑战,要么未能在一个证明周期内成功修复失败扇区,则此扇区上的FaultCount会递增。已经在Failing状态下经过MAX_CONSECUTIVE_FAULTS个挑战的扇区将被终结,并最终产生一个TerminatedFault。
注意:有一点很重要,就是所有的错误都要在挑战生成之前被知晓。否则就将有可能在实际的挑战时间前知道挑战集合。这会允许矿工只报告被挑战扇区上的错误,也就保证了其它出错扇区不被检测到。
出错惩罚
每个被报告的错误都会背负一次惩罚。
矿工上榜
“存储算力一致性”参与者都遵循Minimum Miner Size规则,说明小于MIN_MINER_SIZE_STOR的矿工不能产生有效的“选举PoSt”。
这些矿工的算力不会计算为全部网络算力的一部分,他们也没能力提交选举PoSt,但他们仍然可以运行和以消息的形式传输SurprisePosts并将其添加到链上。
按理说,新的矿工会通过SurprisePoSts上榜,一旦他们达到要求的大小,他们的算力将会被包含进总算力,他们也能使用ElectionPoSt提交新的区块。
PoSt 参数
| 参数 | 类型 | 取值 | 描述 |
|---|---|---|---|
| POST-CHALLENGE-BLOCKS | BLOCKS | 480 | 偏移的时间量,在个时间之前,实际生成PoSt的工作不能开始进行。它是在证明周期结束前的某个间隔值,因此会小于一个证明周期。 |
| POST-CHALLENGE-HOURS | HOURS | 2 | PoSt挑战时间 (参照POST_CHALLENGE_BLOCKS)。 |
| POST-PROVING-PERIOD | BLOCKS | 5760 | 时间间隔,在这期间必须提交一个PoSt。 |
Filecoin规范系列 Filecoin规范①--节点系统 Filecoin规范③--虚拟机系统