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

執行層與共識層用戶端:現代以太坊節點如何運作

自「合併」(The Merge)之後,一個以太坊節點不再是一支程式,而是兩支——一個執行層用戶端與一個共識層用戶端——透過名為 Engine API 的一道狹小私門互通訊息。本篇要講清楚它們如何分工,以及為何「跟大家跑同一支用戶端」正是這個網路最隱性的系統性風險。

一個節點,兩支程式

在 2022 年 9 月之前,跑一個以太坊節點意味著下載一支程式——比方說 Geth——而這一支執行檔包辦一切:它執行交易、保管世界狀態,而且還跟著 工作量證明 去決定哪個區塊才是最新的。「合併」把這支程式乾淨俐落地一刀剖成兩半。如今你跑的節點是兩支獨立的程式,並肩住在同一台機器上,各司其職,彼此之間透過一道狹窄又上了鎖的門互通。

執行層用戶端 回答的是發生了什麼:它把交易丟進 EVM 執行,算出新的 世界狀態共識層用戶端 回答的是以什麼順序、以及是否定案:它跑 權益證明——誰來提出下一個區塊、哪一條分叉才是正統、一個區塊何時起再也無法回頭。兩半都無法獨自撐起整條鏈;一個能運作的節點需要兩者,成對且同步

執行層用戶端:EVM 與世界狀態

執行層用戶端是那個感覺最像舊節點的部分。它維護待處理 交易記憶池(mempool)、透過執行層的點對點網路把交易散播給節點(交易傳播)、在 EVM 裡執行每一筆交易,並更新 世界狀態(每個帳戶餘額、合約儲存槽與 nonce,全部提交進一棵 Merkle Patricia 樹)。它同時對外提供錢包與 dApp 呼叫的 JSON-RPC——像是 `eth_call`、`eth_sendRawTransaction`、`eth_getLogs`。

這正是「合併」帶來的關鍵轉變:執行層用戶端失去了它的共識大腦。它不再決定哪個區塊是鏈的鏈頭,也不再有挖礦或驗證者的概念。它只是把被遞過來的區塊負載(payload)拿來執行、重新檢查,然後回報 `VALID`(有效)或 `INVALID`(無效)。至於該執行哪個負載的決定,如今來自另一支程式。

執行層用戶端有好幾種彼此獨立的實作,每一種都是用不同語言、依同一份規格各自「無塵室」打造出來的:Geth(Go)、Nethermind(C#/.NET)、Besu(Java)、Erigon(Go)與 Reth(Rust)。它們對每一次狀態轉換都必須一個位元不差地一致;而它們由不同團隊、用不同語言寫成,正是整件事的重點所在——這點我們會在最後談到。

共識層用戶端:權益證明與最終性

共識層用戶端(信標節點)負責跑權益證明這套機制。它追蹤 驗證者集合、蒐集他們的 證明(attestation)(驗證者每個時槽投下的票)、跑 分叉選擇規則——LMD-GHOST——從互相競爭的分支中挑出當前的鏈頭,並跑 最終性裝置 Casper FFG定案檢查點。LMD-GHOST 加上 Casper FFG 的組合被命名為 Gasper。時間被切成 時槽(slot)與時段(epoch):一個時槽是 12 秒(一次出塊機會),32 個時槽組成一個時段(6.4 分鐘),也正是決定 最終性 的單位。

關鍵在於,信標節點對 EVM 一無所知。對它而言,執行負載只是一團不透明的資料:它把這團資料帶著走、交給執行層用戶端去驗證,然後只記住產出的區塊雜湊。和執行層一樣,共識層也有好幾種實作——Lighthouse(Rust)、Prysm(Go)、Teku(Java)、Nimbus(Nim)與 Lodestar(TypeScript)。

Engine API:兩者之間的那道門

這兩支用戶端跑在同一台機器上,透過 Engine API 互通——這是一個小巧、需要驗證JSON-RPC 介面,慣例上開在 8551 連接埠,與對外開在 8545 的公開 RPC 完全分開。這道門用一把共享的 JWT 密鑰 上鎖:那是一把 32 位元組的 HS256 金鑰,寫進一個兩支程式都會讀取的 `jwtsecret` 檔。少了這把密鑰,Engine API 會回絕每一筆請求——藉此擋下流氓行程(或打向 localhost 的惡意網站)餵你的節點偽造的區塊。

整段對話只靠三個核心方法撐起。`engine_newPayloadV3` 的意思是「這是一個區塊——把它執行並驗證」。`engine_forkchoiceUpdatedV3` 的意思是「這是新的鏈頭/安全/已定案區塊——另外,可選地,幫我開始建一個區塊」。`engine_getPayloadV3` 的意思是「把你建好的那個區塊給我」。永遠是共識層發話,執行層回應。

# 1. Create the shared JWT secret once: 32 random bytes, hex-encoded
openssl rand -hex 32 > /secrets/jwt.hex

# 2. Execution client (Geth): serve the Engine API on :8551, locked by the JWT.
#    The public JSON-RPC for dApps stays on :8545.
geth --mainnet \
  --authrpc.addr 127.0.0.1 --authrpc.port 8551 \
  --authrpc.jwtsecret /secrets/jwt.hex \
  --http --http.api eth,net,web3

# 3. Consensus client (Lighthouse beacon node): point it at the EL's
#    Engine endpoint, presenting the SAME secret. Mismatch = no connection.
lighthouse bn --network mainnet \
  --execution-endpoint http://127.0.0.1:8551 \
  --execution-jwt /secrets/jwt.hex \
  --checkpoint-sync-url https://mainnet.checkpoint.sigp.io
一組真實的 EL+CL 配對:Geth 與 Lighthouse,靠一把共享的 JWT 密鑰,透過 8551 連接埠上的 Engine API 接合在一起。
  1. 跟隨鏈(你的節點此刻不出塊):信標節點透過共識層的 gossip 網路,從某個節點收到一個新的信標區塊。
  2. 它取出內嵌的執行負載——也就是 EVM 層級、裝著交易與所宣稱新狀態根的區塊——並呼叫 engine_newPayloadV3 把它交給執行層用戶端。
  3. 執行層用戶端重跑每一筆交易、重新算出狀態根,然後回覆 VALID、INVALID 或 SYNCING。INVALID 代表該區塊被否決,永遠不會被出具證明。
  4. 信標節點把結果併入 LMD-GHOST 分叉選擇,接著呼叫 engine_forkchoiceUpdatedV3,把新的鏈頭、安全與已定案區塊雜湊告訴執行層。
  5. 提出區塊(輪到你的驗證者):共識層呼叫 engine_forkchoiceUpdatedV3,但這次帶上負載屬性(時間戳、手續費收款人、提領),告訴執行層從記憶池開始建一個區塊。
  6. 片刻後,共識層呼叫 engine_getPayloadV3 取回建好的負載,把它包進一個信標區塊,交由驗證者用戶端簽章,再廣播給其他節點。

為什麼要把節點剖成兩半?

這在協定層次上就是關注點分離。共識規則可以演進——比方說朝 單時槽最終性 邁進——而完全不必動到 EVM;執行規則也能改動,而不必動到共識。由於 Engine API 是一份穩定的契約,你可以自由搭配:五種執行層用戶端配五種共識層用戶端中的任一個,25 種組合,全部彼此互通。團隊各自獨立、程式碼各自獨立、出的 bug 也各自獨立。

不過也要老實面對代價:一個節點如今是更多會動的零件。兩支程式、兩個磁碟上的資料庫、兩套同步流程、多開一個連接埠,還要備好一把 JWT。兩半都必須上線且步調一致——若執行層還在同步,共識層會回報 `SYNCING`,你的節點就無法出具證明;若 JWT 對不上,兩者乾脆拒絕交談。這份模組化值得,但它的維運面確實比舊時那支單一執行檔大上不少。

用戶端多樣性:網路最隱性的系統性風險

因為每一種用戶端都實作同一份規格,其中一種出了 bug 是可以被圍堵的——前提是那種用戶端在網路裡占的比例不要太大。用戶端多樣性 講的正是這道安全餘裕,而危險就藏在兩個門檻:質押驗證者的三分之一三分之二

最終性 需要三分之二的質押對同一條鏈出具證明。所以,若一個有 bug 的用戶端掌控超過 1/3,而它崩潰或岔出去,剩下的就湊不到三分之二門檻:鏈會繼續出塊,卻停止定案,而 閒置流失(inactivity leak) 會慢慢抽乾卡住那一側的質押,直到最終性恢復——擾動很大,但安全且可復原。真正的災難是超過 2/3:一個大到這種程度、又有 bug 的單一用戶端,可能對一個無效區塊出具證明並把它定案。誠實的少數(不到 1/3)無法回滾已定案的區塊,因此復原將需要一次社群協調的分叉,罰沒那個超級多數——可能摧毀全體質押的三分之一甚至更多。這正是為什麼社群的經驗法則直白得很:任何單一執行層或共識層用戶端都別超過 33%。

這不是紙上談兵。在「合併」前後,Geth 一度占了執行層用戶端約莫 80% 以上的比例——它身上一個共識 bug 就足以把鏈劈成兩半——而在共識端,Prysm 也屢次飄到三分之一以上。真正的壓力測試發生在 2023 年 5 月:連續好幾個時段,鏈持續出塊卻無法定案,原因追查到共識層用戶端(Prysm 與 Teku)在處理一大批「有效但陳舊」的證明時不堪負荷而卡住。正因為並非所有驗證者都跑同一支用戶端,未受影響的那些用戶端撐住了整個網路,最終性在幾分鐘內就恢復了。換成單一用戶端的「單作物」局面,未必能這麼溫和地復原。