# （Import Entry）以太坊：Safe Head 机制介绍（二）

By [imToken Labs](https://paragraph.com/@imtoken-labs) · 2023-02-10

---

![900-383.png](https://storage.googleapis.com/papyrus_images/dd9ac1f38af75be6ab33dc08946b5114b50002f065e45916ea80a8a97fe07aed.png)

900-383.png

原文來源：[https://support.token.im/hc/zh-cn/articles/12173282119321-以太坊-Safe-Head-机制介绍-二-](https://support.token.im/hc/zh-cn/articles/12173282119321-%E4%BB%A5%E5%A4%AA%E5%9D%8A-Safe-Head-%E6%9C%BA%E5%88%B6%E4%BB%8B%E7%BB%8D-%E4%BA%8C-)

作者：Nic Lin，imToken Labs 资深区块链工程师

本文受众：区块链开发者

[上一篇](https://support.token.im/hc/zh-cn/articles/12171197330457)介绍了 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）。

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

1.  查询新的区块并记录区块，包含分叉链的区块也要能查询得到
    
2.  获取区块里的 Attestation 并记录 Attestation
    
3.  针对每个区块，搜寻所有 Attestation.beaconBlockRoot == Block.blockRoot 的 Attestation，去掉重复的 Validator 得出该区块得票数
    
4.  算出每一个 slot 的总 Validator 数量，除上得票数，算出得票率
    

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

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

### 第二步：获取区块里的 Attestation

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

![image3.png](https://storage.googleapis.com/papyrus_images/5adca87c229db690d3f1b74426ddb0955ec0d2b49f5a41765608f1fac35f197e.png)

image3.png

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

![image2.png](https://storage.googleapis.com/papyrus_images/354899081e4cd081355e61485b46ce1daef480d0dfbcf28e263102b3b24bd9a9.png)

image2.png

### 第三步：Aggregation Bits

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

以[slot 4823390 的区块](https://beaconcha.in/slot/4823390#attestations)里所含的第一个 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 所合并而成。

![image1.png](https://storage.googleapis.com/papyrus_images/6e33bce60db3108fe9eee8afa3165510361f77f52eeed5c544dcea76075206b9.png)

image1.png

另外需要注意的是你从节点要回来的 Attestation，里面的 Aggregation Bits 会是经过 SSZ 编码过后的值（一串 Hex String），不是你在上图中看到的格式，所以需要自己先用 SSZ 解码（可以参考[ChainSafe 的 Typescript SSZ 套件](https://www.npmjs.com/package/@chainsafe/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](https://ethereum.github.io/beacon-APIs/)），回传的资料会包含该 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 中找到最近一个非空区块。

![image5.png](https://storage.googleapis.com/papyrus_images/6ee946948c329e850683d848a06308f3d32b2d6baf452bf63a663733b82c7c85.png)

image5.png

第一篇及以上部分算是介绍完了 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](https://ethresear.ch/t/analysis-of-bouncing-attack-on-ffg/6113)。

### 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 区块可靠许多。

**参考资料**
--------

*   [Balancing Attack: LMD Edition - Consensus - Ethereum Research](https://ethresear.ch/t/balancing-attack-lmd-edition/11853)
    
*   [Analysis of bouncing attack on FFG - Proof-of-Stake - Ethereum Research](https://ethresear.ch/t/analysis-of-bouncing-attack-on-ffg/6113)
    
*   [Upgrading Ethereum | One Page Annotated Spec](https://eth2book.info/altair/annotated-spec)
    
*   [Eth Beacon Node API v2.3.0 - Eth2Spec v1.1.0 OAS3](https://ethereum.github.io/beacon-APIs/)
    
*   [https://beaconcha.in](https://beaconcha.in/)
    
*   [@chainsafe/ssz - npm](https://www.npmjs.com/package/@chainsafe/ssz)
    

_特别感谢_ [_Chih-Cheng Liang_](https://medium.com/u/5c031577a87d)，[_Chang-Wu Chen_](https://medium.com/u/e77f8b591fa3)，Steven Wu 和\*[doublespending](https://medium.com/u/2a4e8d3cc984)\*\* 校对本文并提供改进建议。\*

风险提示：本文内容均不构成任何形式的投资意见或建议。 imToken 对本文所提及的第三方服务和产品不做任何保证和承诺，亦不承担任何责任。数字资产投资有风险，请谨慎评估该等投资风险，咨询相关专业人士后自行作出决定。

---

*Originally published on [imToken Labs](https://paragraph.com/@imtoken-labs/import-entry-safe-head)*
