newsence

Engine API 的二進位 SSZ 傳輸:在 Kurtosis 實體網路上的初步基準測試

ethresear.ch·27 天前

本文展示的基準測試顯示,在以太坊 Engine API 中將 JSON-RPC 替換為二進位 SSZ 編碼,能顯著降低負載大小與編碼延遲,特別是在 Fulu 分叉後 Blob 數量增加的情況下更為明顯。

作者: Giulio Rebuffo

簡介

SSZ Engine API 規範 提議將以太坊 Engine API(共識層 (CL) 與執行層 (EL) 客戶端之間的介面)的 JSON-RPC 編碼替換為二進位 SSZ (Simple Serialize)。

目前,CL 與 EL 之間的每條訊息都採用 JSON 編碼:每筆交易、Blob 和提款的每個位元組都經過十六進位編碼並封裝在 JSON 中。對於像 forkchoiceUpdated 這樣的小型負載,這沒問題。但對於返回完整 ExecutionPayloadBlobsBundleengine_getPayloadV5 來說,編碼開銷已成為區塊傳播延遲的重要因素。

傳輸層

兩種傳輸方式共存於同一個 Engine API 連接埠(預設為 8551):

傳輸方式Content-Type端點
JSON-RPCapplication/jsonPOST /
SSZ RESTapplication/octet-streamPOST /engine/v{N}/{resource}

隨著 PeerDAS 和 Fulu 分叉將 Blob 數量從 6 個推升至 72 個(目標 48 個),通過 Engine API 的負載大小將顯著增長。本文展示了在運行三個具有 SSZ 傳輸檢測功能的 EL 實作的 live Kurtosis 測試網上的編碼基準測試結果。

方法論

測試網設置

部署了一個 Kurtosis 測試網,包含 12 組 EL+CL 配對 —— 涵蓋三個 EL 客戶端(Geth、Erigon、Nethermind)和四個 CL 客戶端(Prysm、Lighthouse、Teku、Lodestar)的所有組合 —— 全部運行支持 SSZ 傳輸和編碼檢測的自定義 Docker 鏡像。

網路參數:6 秒 slot、Genesis 即啟用 Fulu 分叉、最大 72 個 Blob目標 48 個 Blob、每個節點 32 個驗證者。交易負載使用 spamoor 產生,速率為 200 EOA tx/s 加上 Blob 交易(每個包含 6 個 sidecar)。

測量方式

每個 EL 都經過檢測,以 同時 使用 SSZ 和 JSON 編碼每個 GetPayloadV5 響應,並記錄各自的大小和時間:

GetPayloadV5 encoding: SSZ=9,930,470 bytes in 19,700 us | JSON=19,906,846 bytes in 59,531 us | ratio=2.0x size, 3.0x time blobs=72

結果

72 個 Blob 下的 getPayload 編碼時間 (Fulu 最大值)

在連續運行 17 小時後,觀察到 72 個 Blob(約 9.9 MB SSZ / 19.9 MB JSON)下各 EL+CL 配對的最差編碼時間:

CL \ ELNethermindGethErigon
PrysmSSZ 5 ms / JSON 103 ms (20.6×)SSZ 19 ms / JSON 180 ms (9.3×)SSZ 15 ms / JSON 63 ms (4.2×)
LighthouseSSZ 8 ms / JSON 114 ms (14.3×)SSZ 24 ms / JSON 181 ms (7.5×)SSZ 6 ms / JSON 82 ms (12.7×)
TekuSSZ 14 ms / JSON 120 ms (8.6×)SSZ 28 ms / JSON 358 ms (12.8×)SSZ 13 ms / JSON 81 ms (6.1×)
LodestarSSZ 8 ms / JSON 91 ms (11.4×)SSZ 36 ms / JSON 446 ms (12.3×)SSZ 31 ms / JSON 101 ms (3.3×)

這些是 17 小時運行中的最差情況測量值(觀察到的最高編碼時間)—— 現實中的典型時間會更低,但最差情況的延遲對於區塊傳播截止時間至關重要。

關鍵觀察:

  • 在最差情況下,所有 12 個配對的 SSZ 編碼時間均保持在 36 ms 以下 —— 無論何種實作,效能始終穩定。
  • JSON 的最差情況編碼時間從 63 ms 到 446 ms 不等 —— 在不同 EL 實作之間差異極大。
  • Geth 的 JSON 開銷最高 (180–446 ms),因此 SSZ 的加速效果在那裡最為顯著 (7.5–12.8×)。
  • Nethermind 的最差 JSON 編碼時間在 91–120 ms 之間,SSZ 快了 8.6–20.6×。
  • Erigon 介於兩者之間 (63–101 ms JSON),SSZ 加速比為 3.3–12.7×。

傳輸大小

在所有攜帶 Blob 的負載中表現一致:

負載SSZ 大小JSON 大小比例
72-blob 區塊~9.9 MB~19.9 MB2.0×
24-blob 區塊~3.3 MB~6.6 MB2.0×
6-blob 區塊~837 KB~1.68 MB2.0×

2 倍的比例是結構性的:JSON 十六進位編碼會使每個位元組翻倍(0xff → "0xff" = 4 個字元)。

其他 Engine API 方法

並非所有 Engine API 調用都能從 SSZ 中獲得同等收益。每個 slot 調用的三個方法是 forkchoiceUpdatednewPayloadgetPayload。以下是它們在請求/響應大小和編碼時間方面的比較(所有測量值來自 Erigon + Prysm 配對):

訊息大小:

方法方向SSZ 大小JSON 大小
forkchoiceUpdatedCL → EL 請求100–200 B300–600 B
forkchoiceUpdatedEL → CL 響應49–57 B~200 B
newPayloadCL → EL 請求603 B – 46 KB1.4 KB – 92 KB
getPayloadEL → CL 響應837 KB – 9.9 MB1.68 MB – 19.9 MB

編碼時間:

方法SSZ 時間JSON 時間
forkchoiceUpdated<1 µs<1 µs
newPayload15–45 µs60–4,978 µs
getPayload1.1–25.6 ms16–211 ms

forkchoiceUpdated 在兩個方向上都非常小 —— 低於 200 位元組。在這種規模下,SSZ 和 JSON 之間的開銷差異可以忽略不計。

newPayload 攜帶 ExecutionPayload(交易、提款)但不包含 Blob —— Blob 通過 gossip 層傳播。即使在高吞吐量的主網區塊(約 1,500 筆交易)中,newPayload 在 SSZ 中大約為 200–400 KB。在這些大小下,無論格式如何,編碼開銷都遠低於 1 ms。

getPayload 是一個異類。它返回完整的 ExecutionPayload 加上 BlobsBundle(承諾、證明和所有 Blob 數據)。在 72 個 Blob 時,這單個響應在 SSZ 中為 9.9 MB(JSON 中為 19.9 MB)—— 比任何其他 Engine API 訊息大幾個數量級。這就是編碼開銷變得可以以毫秒計量的地方,也是為什麼 SSZ 傳輸規範將其影響力集中於此的原因。

結論

Engine API 的 getPayload 響應是每個 slot 中 CL 和 EL 之間交換的最大訊息。在 72 個 Blob 時,它在 SSZ 中攜帶約 9.9 MB 的數據(JSON 中為 19.9 MB),這單個編碼步驟在 JSON 中可能耗時 63 ms 到 446 ms,具體取決於客戶端,這直接消耗了區塊傳播的預算。

在所有 12 個測試的 EL+CL 配對中,相同負載的 SSZ 編碼僅需 5–36 ms。加速範圍從 3× 到 20× 不等,且對於大多數實作而言,SSZ 的最差情況時間仍遠低於 JSON 的最佳情況時間。

傳輸大小的縮減在所有負載大小中始終保持 2× —— 這是消除十六進位編碼的結構性結果。對於一個 72-blob 區塊,這意味著 CL↔EL 鏈路每個 slot 可節省約 10 MB。

採用的成本很低:兩種傳輸方式共存於同一個連接埠,JSON-RPC 仍為預設方式,而 SSZ 通過 content-type 協商選擇性加入。Geth、Erigon、Nethermind、Prysm、Lighthouse、Teku 和 Lodestar 均已有實作。

重現

Kurtosis 測試網配置

participants:
  # 3 ELs × 4 CLs = 12 pairs
  - el_type: geth
    cl_type: prysm
    supernode: true
    validator_count: 32
  - el_type: geth
    cl_type: lodestar
    supernode: true
    validator_count: 32
  - el_type: geth
    cl_type: teku
    supernode: true
    validator_count: 32
  - el_type: geth
    cl_type: lighthouse
    supernode: true
    validator_count: 32
  - el_type: erigon
    cl_type: prysm
    supernode: true
    validator_count: 32
  - el_type: erigon
    cl_type: lodestar
    supernode: true
    validator_count: 32
  - el_type: erigon
    cl_type: teku
    supernode: true
    validator_count: 32
  - el_type: erigon
    cl_type: lighthouse
    supernode: true
    validator_count: 32
  - el_type: nethermind
    cl_type: prysm
    supernode: true
    validator_count: 32
  - el_type: nethermind
    cl_type: lodestar
    supernode: true
    validator_count: 32
  - el_type: nethermind
    cl_type: teku
    supernode: true
    validator_count: 32
  - el_type: nethermind
    cl_type: lighthouse
    supernode: true
    validator_count: 32
network_params:
  seconds_per_slot: 6
  fulu_fork_epoch: 0
  bpo_1_max_blobs: 72
  bpo_1_target_blobs: 48
additional_services:
  - spamoor
spamoor_params:
  spammers:
    - scenario: eoatx
      config:
        throughput: 200
        max_pending: 400
        max_wallets: 200
    - scenario: blob-combined
      config:
        throughput: 40
        max_pending: 80
        sidecars: 6

kurtosis run github.com/ethpandaops/ethereum-package --args-file ssz_bench_net.yml

實作項目

https://ethresear.ch/t/binary-ssz-transport-for-the-engine-api-an-initial-benchmark-on-a-live-network-with-kurtosis/24324