以太坊:Safe Head 机制介绍(二)
来源:    发布时间: 2023-12-28 17:43   62 次浏览   大小:  16px  14px  12px
上一篇介绍了 Safe Head 机制,这一篇将介绍 imToken 尝试实践的 Safe Head 版本以及除了 Safe Head 之外能做的事,最后会介绍 Casper FFG 以及该怎么使用 Checkpoint 和 Safe Head。

上一篇介绍了 Safe Head 机制,这一篇将介绍 imToken 尝试实践的 Safe Head 版本以及除了 Safe Head 之外能做的事,最后会介绍 Casper FFG 以及该怎么使用 Checkpoint 和 Safe Head。


上一篇最后有提到 Safe Head 算法还没落地,虽然目前 PoS 运作都正常,但我们在 imToken 仍尝试设计出自己的 Safe Head 版本,希望在过渡期能获得比 Block Confirmation Rule 更可靠的区块参考,让使用者的体验比较不会受到网络波动所影响。


过渡期及 Safe Head 之外能做的事

imToken 在尝试自己的 Safe Head 版本

目前的版本是由 Block Confirmation Rule 加上得票率的筛选,例如未来三个区块得票率都大于 90%,或是未来四个区块得票率都大于 70%。如此虽然比单纯 Block Confirmation Rule 还可靠,但只单纯看未来 X 个区块存在无法反映实时投票率变化的缺点。未来还需要更多的迭代和改进。


那除了 Safe Head,还有什么是现在我们能做的呢?


监控区块及 epoch 投票率

PoS 的优点之一是我们能透过观察投票状况来提前察觉网络是否有问题、攻击是否正在发生等等,能够有一个监控系统来监测可以让我们提前做出反应,不管是送出警报、拉高 Safe Head 门槛,或是将 Safe Head 设回一个更保守的区块(例如 Justified Checkpoint)。


而这些都只需要算出区块投票率即可,也就是第一篇提到的步骤


查询新的区块并记录区块,包含分叉链的区块也要能查询得到


获取区块里的 Attestation 并记录 Attestation


针对每个区块,搜寻所有 Attestation.beaconBlockRoot == Block.blockRoot 的 Attestation,去掉重复的 Validator 得出该区块得票数


算出每一个 slot 的总 Validator 数量,除上得票数,算出得票率


注:计算 epoch 投票率会在第三步和区块投票率不太一样,在后面会再补充解释。


其中第二步、第三步及第四步在实践上有一些需要注意的地方:


第二步:获取区块里的 Attestation

同一个 slot 且同一个 committee 的 Validator 所产生的 Attestation 不会总是被完美合并成一个,所以会常常出现同一个 slot 同一个 committee 的 Validator 的 Attestation 在不同区块被收录。如果你透过 beaconcha.in 来查询一个区块的话,你会看到投给它的 Attestation 分散在不同区块,以区块 4835000 为例,你可以看到虽然大多数的 committee 的 Attestation 都在下一个 slot 4835001 被收录,但仍有些投票是在后面的 slot 才被收录:


所以在资料库里要唯一识别「同一个 slot 且同一个 committee 的 Validator 所产生的 Attestation」会有点麻烦,要不 (1) 遇到同样的 Attestation 但在不同区块收录时,将 Validator 合并起来,但如此就没办法呈现像上图那样的资讯,也分辨不出合并过哪些 Attestation;要不 (2) 用 Attestation 被收录的区块号码及 Attestation 被收录的排序来识别 Attestation,如此就能分得出同样的 Attestation 在不同区块被收录的情况。


第三步:Aggregation Bits

每个 Attestation 会有一项叫做 Aggregation Bits,这是一个 bit 阵列,用来记录这个 Attestation 是由 committee 中的哪几个 Validator 的 Attestation 所合并而来。


以slot 4823390 的区块里所含的第一个 Attestation 为例,其 Committee Index 为 42,代表这个 Attestation 是由 committee 42 的 Validator 的 Attestation 所合并而成。另外其 Aggregation Bits 为 214 个 bits,其中只有 4 个 bit 是 1,代表这个 Attestation 是由 committee 42 一共 214 个 Validator 中第 28、第 58、第 84 及第 147 位 Validator 的 Attestation 所合并而成。


另外需要注意的是你从节点要回来的 Attestation,里面的 Aggregation Bits 会是经过 SSZ 编码过后的值(一串 Hex String),不是你在上图中看到的格式,所以需要自己先用 SSZ 解码(可以参考ChainSafe 的 Typescript SSZ 套件)。以下是解码上面这个 Attestation 的 Aggregation Bits 的范例:


import { BitArray, BitListType } from "@chainsafe/ssz"


const committeeSize = 214


// Raw aggregation bits are hex string


const rawAggregationBits = "0x000000080000000200000800000000000000040000000000000040"


// Remove 0x prefix


rawAggregationBits = rawAggregationBits.substring(2)


// Convert raw aggregation bits to byte array


const byteArraySize = committeeSize / 8 + 1


const byteArray = new Uint8Array(byteArraySize)


for (let c = 0; c < rawAggregationBits.length; c += 2) {


   const byte = parseInt(rawAggregationBits.substring(c, c + 2), 16)


   byteArray[c / 2] = byte


}


// Deserialize byte array to bit list with SSZ library


const CommitteeBits = new BitListType(byteArraySize * 8)


const aggregationBitList =  


   CommitteeBits.deserialize(byteArray)


       .toBoolArray()


       .map((v) => (v ? 1 : 0))

第四步:算出 Slot 的 Validator 数量

实际上要获取每一个 slot 确切的 Validator 数量会需要用 eth/v1/beacon/states/{slot}/committees 这个 API(这里可以参考更多的 Eth2 API),回传的资料会包含该 slot 每一个 committee 所有的 Validator 的编号,加总所有 committee 的 Validator 数量就能得到该 slot 确切的 Validator 数量。但如果不要求精准的话其实也可以直接将当前 Validator 总数除以 32 个 slot(例如 438989 / 32 ~= 13718)。


计算 epoch 投票率

前面有提到 epoch 投票率和区块投票率在计算上不太一样。计算区块得票率时,要找的是投给该区块的 Attestation,也就是 Attestation.beaconBlockRoot == Block.blockRoot,但 epoch 的投票目标则会是 epoch 第一个区块的 blockRoot。


计算 epoch 得票数:搜寻所有 Attestation.epochTargetRoot == getEpochFirstBlock(epoch).blockRoot 的 Attestation,去掉重复的 Validator 得出该 epoch 得票数。


epoch 总投票数则是加总该 epoch 每个 slot 的 Validator 数量,再除上得票数即能得到 epoch 得票率。


注:如果 epoch 第一个 slot 是空区块,则往前从过去的 slot 中找到最近一个非空区块。




第一篇及以上部分算是介绍完了 Safe Head 的机制,最后这边再搭配 Casper FFG 的介绍,让 DApp 开发者或使用者能知道如何来利用这两个工具。


Casper FFG

Casper FFG 是以 epoch 为单位的共识机制,一个 epoch 要先获得超过 2/3 Validator 投票成为 Justified Checkpoint,接着再获得一次超过 2/3 投票才会变成 Finalized Checkpoint。


Justified Checkpoint

一个 epoch 要变成 Justified 最快要经过一轮的投票,也就是一个 epoch,6.4 分钟。但变成 Justified 后还不代表是真的安全的,攻击者还是能让两条分叉链上的 epoch 轮流变成 Justified,导致一直没有新的 epoch 能变成 Finalized。虽然新的区块还是会一直被 propose 出来,但从 Casper FFG 的角度来看,共识机制基本上停摆了,即共识机制的 liveness 被破坏。


不过要能攻击成功需要攻击者占有一定的 Validator 数量,以及网络要出现问题导致 Validator 的投票无法实时传递到网络的另一端。


更多介绍可以参考 Bouncing Attack。


Finalized Checkpoint

一个 epoch 要变成 Finalized 最快要经过两轮的投票,也就是两个 epoch,12.8 分钟。虽然比较久但是安全非常非常多,攻击者要能成功让两条分叉链上的 epoch 被 Finalized 不只需要攻击者占有超过 1/3 的 Validator,以及网络出现问题,攻击者在事后更会被 slash 至少 1/3 的 Validator,1/3 Validator抵押的 Ether 目前约等价于 72 亿美元。这样的攻击破坏的是共识机制的安全性。


要怎么使用 Checkpoints 及 Safe Head?

用 Checkpoint 来当作 Finality

在 PoW 里,每个 DApp 都只能自己主观预估一个 Block Confirmation Number 来确保 Finality,但在 PoS 里,协议本身就提供一个客观的 Finality,虽然等待的时间可能比 Block Confirmation Rule 还久(看你等几个区块),但安全性会远胜于 Block Confirmation Rule。


当你在查询某个链上状态时,你可以透过指定 Block Tag 为 finalized,节点就会回传给你 Finalized Checkpoint 那当下的状态:


await provider.getBalance("vitalik.eth", "finalized")

用 Safe Head 呈现即时资讯

DApp 需要 Finality 的话可以使用 Checkpoint,那在平时前端显示画面给使用者时,数据要参考什么时间点的呢?总不可能显示久久才更新一次的 Checkpoint 时间点的信息吧?


在 PoW 中 DApp 都是拉 latest 区块的资讯来显示,也就是节点看到的最新区块。但 PoS 中 latest 区块不再那么可靠,这时就可以用 safe 区块的信息来显示,虽然会延迟四秒,但是比 latest 区块可靠许多。