JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

在正式環境運行驗證者:金鑰、MEV-Boost 與在線率

專業質押的重點不在於多賺,而在於永不虧損。本文教你如何守護簽署金鑰、為何兩台機器共用同一把金鑰會讓質押瞬間蒸發、MEV-Boost 如何外包區塊建構,以及讓你遠離罰沒紀錄的在線率紀律。

兩台機器的故事

2021 年 2 月,一家備受敬重的質押服務商 Staked,在一個下午眼睜睜看著旗下約 75 個驗證者被罰沒。沒有駭客,也沒有什麼奇特的程式錯誤。他們做的,是維運上最自然不過的事——建立一套冗餘的高可用架構,好讓一台機器掛掉時由備援接手。問題在於,那台備援機跑著一個載入了相同簽署金鑰的驗證者用戶端,而有那麼一段時間,兩台機器同時都活著,於是兩邊都盡責地簽了名。看在網路眼裡,一把金鑰簽署了兩則互相矛盾的訊息——這正是作弊的教科書定義——於是協定做了它承諾要做的事:銷毀質押。

這個故事正是本文的縮影。在正式環境運行驗證者,重點其實不在於榨出額外收益,而是一門不虧損的紀律。你有三項工作,而它們的優先順序永遠不變:守好金鑰使其不被竊取、絕不簽署兩則互相矛盾的訊息、保持在線。先前的階梯教過你驗證者做什麼——在權益證明之下提交證明、提議區塊、賺取獎勵;這一階要談的,是專業人士如何讓這台機器穩穩跑上好幾年,而不至於落入罰沒紀錄。

兩把金鑰,各自能做什麼

一個驗證者由兩把風險屬性截然不同的金鑰所掌控。簽署金鑰是一把熱端的 BLS12-381 金鑰(產生BLS 簽章),它必須在每一個 epoch 都在線,以提交證明、偶爾提議區塊。提款憑證則決定錢最終能去哪裡。現代的做法採用 `0x01` 憑證,它指向一個普通的以太坊執行層位址,你只設定一次,之後便不可更改:所有提款——無論是超過 32 ETH 部分獎勵的定期掃出,或驗證者退出時的全額餘額——都會自動流向那個位址,別無他處。

這種拆分是一項刻意設計的安全特性,值得徹底搞懂:偷走你簽署金鑰的攻擊者,能做什麼、又不能做什麼。他們對你進行騷擾式攻擊:透過重複簽署讓你被罰沒,或送出一筆自願退出,把你的驗證者強制踢出活躍集合。但他們無法竊取資金——錢依然只會流向你的 `0x01` 提款位址,而那個位址可以放在一台永不連網的機器上、存於冷儲存之中。所以,簽署金鑰是你必須保持可用的金鑰;提款金鑰(或能推導出兩者的助記詞)則是你必須保持安全的金鑰。

# Derive the BLS signing key + keystore from a fresh mnemonic
# (EIP-2333/2334 key tree; EIP-2335 password-encrypted keystore JSON)
deposit new-mnemonic \
  --num_validators 1 \
  --chain mainnet \
  --eth1_withdrawal_address 0xYourColdExecutionAddress   # sets 0x01 creds

# Output, per validator:
#   keystore-m_12381_3600_0_0_0-<ts>.json   <- signing key, encrypted
#   deposit_data-<ts>.json                  <- 32 ETH deposit + withdrawal creds
#
# The mnemonic re-derives EVERYTHING. It should NOT live on the signing box;
# back it up offline (paper / steel / Shamir shares) and walk away.
產生驗證者金鑰,並把提款鎖定到一個你掌控的冷端位址。

在正規的維運中,簽署金鑰很少就這麼擺在一個檔案裡、緊鄰著驗證者用戶端。常見的做法是使用遠端簽署器(例如 Web3Signer):驗證者用戶端透過區域網路請它簽署每一項職責,金鑰在靜態時加密、或保存於 HSM(硬體安全模組)中,而且關鍵在於——這個簽署器自己保有一份防罰沒資料庫。更深一層的重點是,金鑰素材是透過金鑰推導函式從單一助記詞確定性地推導出來的,所以握有那組種子的人,就握有了一切。

罰沒保護資料庫:那個會說「不」的良知

一個行為良好的驗證者,只有兩種方式會被罰沒,而兩者都是違反某項罰沒條件。第一種是為同一個 slot 提議兩個不同的區塊。第二種是一筆有問題的證明:對同一個 target epoch 投出兩票(重複投票),或投出一票、其 source/target 區間包覆住先前的一票(包覆投票)。注意這些情況的共通點——它們全都要求金鑰去簽署一則就其自身歷史而言根本不該簽的東西。而那段歷史,正是罰沒保護資料庫所記得的。

這個資料庫很小、概念上也很單純:對每一把公鑰,它記下曾簽署過的最高區塊 slot,以及曾簽署過的最高證明 source/target epoch。在釋出任何簽章之前,用戶端會拿請求去比對這份紀錄,並拒絕任何會倒退或衝突的東西。順序很重要:它在交出簽章之前就先記下意圖,這樣即使在簽署過程中當機,也只會讓它變得保守,絕不會更寬鬆。

# A validator client MUST consult its local DB before every signature.
function may_sign_block(pubkey, slot):
    last = db.max_signed_block_slot(pubkey)
    if last is not None and slot <= last:
        refuse("second block at/below an already-signed slot")
    db.record_block(pubkey, slot)         # commit BEFORE releasing signature
    return sign

function may_sign_attestation(pubkey, source, target):
    if target <= db.max_signed_target(pubkey):
        refuse("double vote: target epoch not strictly increasing")
    if source <  db.max_signed_source(pubkey):
        refuse("surround vote: source epoch moved backwards")
    db.record_attestation(pubkey, source, target)
    return sign
橫亙在你的金鑰與一筆可被罰沒簽章之間的那兩道檢查。

正因為這道防護存在於一個本機檔案中,把驗證者從一台機器搬到另一台,是它一生中風險最高的時刻。各用戶端約定了一種標準匯出格式——EIP-3076 交換 JSON——正是為了在你遷移時把簽署歷史一併帶走,讓新用戶端拒絕簽署任何低於舊用戶端已達到之 slot 的東西。絕不要在一把金鑰上啟動一個全新的、未匯入此檔的用戶端——一個空白的資料庫,會樂呵呵地簽下一個你早已簽過的 slot。

{
  "metadata": {
    "interchange_format_version": "5",
    "genesis_validators_root": "0x043db0...c43efb"
  },
  "data": [{
    "pubkey": "0xb845089a1457f811bfc000d4f76e09",
    "signed_blocks":       [{ "slot": "8195201" }],
    "signed_attestations": [{ "source_epoch": "2290", "target_epoch": "3007" }]
  }]
}
一份 EIP-3076 交換檔:新用戶端要保持安全所需的最小歷史紀錄。

搞清楚一次失誤究竟代價多大,會很有幫助,因為它能解釋底下每一個近乎偏執的習慣。在以太坊主網上,一次罰沒會帶來一筆最高約 1 ETH 的立即懲罰(你有效餘額的 1/32)、一趟被強制送入退出佇列的旅程,以及——把失誤變成災難的那一部分——一筆約 18 天後才計算的關聯懲罰,其大小隨同一時間窗內被罰沒的其他質押量而放大。只罰沒一個驗證者,關聯懲罰很小;若一次罰沒掉網路中相當大的比例,它就會逼近你的全部餘額。正是這個事實,讓專業驗證者經濟學的其餘部分,全都執著於把故障去關聯化

在不重複簽署的前提下做冗餘

這就是整份工作的核心張力。所有在網頁伺服器上訓練出來的直覺都告訴你:為了高可用,跑一台熱備援、必要時切換過去。把這個直覺套用在驗證者用戶端上、把它的金鑰複製到第二台機器,你就一模一樣地重建了 Staked 的災難——因為罰沒保護資料庫是本機的,而兩個資料庫之間並不互通。各自都會樂呵呵地簽名,誰也不知道對方做了什麼。那個天真的容錯移轉,本身就是攻擊。

  1. 一個簽署者,多雙眼睛。 只跑一個驗證者用戶端,但把它連接到兩、三個各自獨立的 beacon(共識層)節點。Beacon 節點從不簽署任何東西,所以複製它們是免費的韌性;某一個落後或當機時,用戶端還能透過另一個繼續提交證明。會害死你的是複製簽署者——複製它的資料來源並不會。
  2. 分身偵測(doppelganger detection)。 在(重新)啟動時,設定用戶端在簽署任何東西之前,先聆聽 2–3 個 epoch,看看網路上有沒有來自它自己公鑰的證明。如果它在網路上「聽到了自己」,代表已經有另一個實例在跑——於是它中止,而不是去當那第二個簽署者。這是你對抗失敗容錯移轉的安全帶。
  3. 透過 DVT 達成真正的容錯。 分散式驗證者技術(Obol、SSV)使用分散式金鑰生成把一把驗證者金鑰拆成多個分片,必須有達門檻數量的營運者協力,才能產生一個門檻簽章。如此一來,驗證者是單一的邏輯簽署者,任何單一節點掛掉它都能存活——這是有冗餘、卻沒有第二把金鑰會相撞的做法。這是唯一能在不招來罰沒的前提下,取得真正高可用的途徑。

MEV-Boost:把你難得才輪到建構的區塊外包出去

在百萬以上的驗證者規模下,你旗下任何一個驗證者,大約每隔幾個月才輪到提議一次區塊。但那次難得提議的價值卻極不平均:絕大部分油水來自最大可提取價值——套利、清算、偶爾出現的三明治攻擊——而一個天真地只憑自己記憶池視野來建構區塊的驗證者,幾乎一點也撈不到。MEV-Boost 正是那套標準軟體,它在協定之外實作了提議者—建構者分離,把這道缺口補上。

其流程是一套謹慎的「承諾—揭露」。專門的區塊建構者彼此競爭,組裝出價值最高的區塊。他們把這些區塊連同出價,提交給一個中繼站,中繼站把每個區塊的本體託管起來,只揭露它的標頭與出價。輪到你的 slot 時,你的 MEV-Boost 挑出最高的那個出價,你的驗證者便簽署一個盲簽(blinded)的區塊標頭——在還沒看到內容之前,就承諾提議那個區塊。直到此時,中繼站才公布完整本體。你無從偷看、也無法捲走別人的交易;而萬一中繼站作惡,頂多讓你錯過一個 slot,卻無法害你被罰沒。

# mev-boost: poll several relays; fall back to a LOCAL block if no bid clears min-bid
mev-boost -mainnet -relay-check -min-bid 0.05 \
  -relays \
    https://[email protected],\
    https://[email protected],\
    https://[email protected]

# Consensus + validator client point at the local mev-boost endpoint:
lighthouse bn --builder http://127.0.0.1:18550
lighthouse vc --builder-proposals \
               --suggested-fee-recipient 0xYourFeeAddress
同時掛上多個中繼站並設定 min-bid 下限,讓你在無利可圖時退回本地自建區塊,而非審查或漏掉。

便利是真的,但取捨同樣真實,而專業人士會把它們一一點名。你正在信任中繼站:它可能不揭露區塊(你就吃下一個漏掉的 slot);而有些中繼站是 OFAC 合規的,會濾掉受制裁的交易——把你的區塊外包給它們,等於悄悄地把你的審查政策也外包了出去。把提議集中在少數幾個中繼站,對網路而言也是一股中心化壓力。緩解之道已內建在上面的設定裡:同時掛上數個中繼站、其中包含抗審查的;並設定一個 `min-bid`,這樣只要可得的 MEV 微不足道,你的用戶端就直接在本地自建區塊、保持中立。

監控、用戶端多樣性,與一份不會反咬你的操作手冊

你無法守住你沒在量測的東西。一個正式環境的驗證者會接上 Prometheus 與 Grafana,並設定能呼叫真人的告警,盯著少數幾項指標,把看不見的偏移化為看得見的警報:證明的納入距離(inclusion distance)與有效性(你的票有沒有及時上鏈?)、漏掉的證明與漏掉的提議、餘額隨時間的走勢、beacon 節點的對等節點數,以及執行層與共識層用戶端是否已同步、並透過 Engine API 互相溝通。一個在凌晨三點悄悄停止提交證明的驗證者,只會一路緩緩失血,直到有人發現為止。

還有一道防線,在災難來臨前都是隱形的:用戶端多樣性。如果單一一種共識層或執行層用戶端的實作,跑在超過三分之二的驗證者上、又出了一個共識錯誤,它可能讓那個超級多數一起去證明一條壞鏈、並被一同關聯罰沒——而這正是關聯懲罰被設計來「使其毀滅性」的那種情境。因此,正規的質押基礎設施會刻意去跑少數派用戶端,以一點額外的維運摩擦,換取不落在某單一專案錯誤的爆炸半徑之內。

最後,把你的容錯移轉寫成一份操作手冊,並反覆演練,因為容錯移轉正是那個唯一可能害你被罰沒的例行操作。整件事的重點,在於保證舊的簽署者確實已死亡、且其歷史已被承接,然後新的那個才開始簽署:

  1. 確認舊主機是真的死了,而不只是連不上——一個看起來掛了、稍後卻復活並開始簽署的節點,是讓自己被罰沒的經典途徑。如果你無法證明它已死,就把它當成還活著。
  2. 停掉舊主機上的驗證者用戶端,並停用它的自動啟動,這樣一次重開機或編排系統,都不會在你背後把它重新拉起來簽名。
  3. 從舊主機匯出罰沒保護資料庫(EIP-3076 交換檔),並匯入到新主機,讓新用戶端知道已簽署過的最高 slot 與 epoch。
  4. 啟動新的驗證者用戶端,開啟分身偵測,讓它在簽署任何一件事之前,先觀察網路 2–3 個 epoch。
  5. 直到此時,才在你的告警系統中把它升為主要節點。把這段空檔裡漏掉的少數幾筆證明,當成「永不重複簽署」那個正確選擇的低廉代價,欣然接受。

把這一切兜在一起,專業的驗證者營運者,比起追逐收益的交易員,更像一位航空機師:對那唯一一組助記詞近乎執念、對罰沒保護資料庫奉若信仰、對「同時跑兩個簽署者」這件事極度偏執,卻對一點點當機處之泰然。當金鑰被守好、節點平穩運轉,你便打好了下一篇指南將組裝成完整 dApp 技術堆疊——錢包、RPC、合約、前端——的基石,讓一般使用者在完全不知道你名字的情況下,安心信任它。