以太坊 Safe Head 機制介紹(二)

NIC Lin
imToken
Published in
12 min readNov 4, 2022

上一篇介紹了 Safe Head 機制,這一篇將介紹 imToken 嘗試實作的 Safe Head 版本以及除了 Safe Head 之外能做的事,最後會介紹 Casper FFG 以及該怎麼使用 Checkpoint 和 Safe Head

Photo by Nick Fewings on Unsplash

上一篇最後有提到 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 為例,你可以看到雖然大多數的 committee 的 Attestation 都在下一個 slot 4835001 被收錄,但仍有些投票是在後面的 slot 才被收錄:

Committee 10 其中一個 Validator(編號 248996)的 Attestation 在 slot 4835010 才被收錄

所以在資料庫裡要唯一識別「同一個 slot 且同一個 committee 的 Validator 所產生的 Attestation」會有點麻煩,要不 (1) 遇到同樣的 Attestation 但在不同區塊收錄時,將 Validator 合併起來,但如此就沒辦法呈現像上圖那樣的資訊,也分辨不出合併過哪些 Attestation;要不 (2) 用 Attestation 被收錄的區塊號碼及 Attestation 被收錄的排序來識別 Attestation,如此就能分得出同樣的 Attestation 在不同區塊被收錄的情況。

區塊 4835000 收錄的 65 個 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 所合併而成。

這四位 Validator 的編號分別是 218385、32675、220759 及 323143

另外需要注意的是你從節點要回來的 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 中找到最近一個非空區塊。

Epoch 10 第一個區塊是空區塊,則投給 epoch 10 的票要填入 epoch 9 最後一個區塊

第一篇及以上部分算是介紹完了 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 億美元。這樣的攻擊破壞的是共識機制的 safety。

要怎麼使用 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 區塊可靠許多。

--

--