Gossipsub 的部分消息擴展和單元級傳播
或者如何讓 blob 傳播更快、更高效
感謝 Raúl Kripalani、Alex Stokes 和 Csaba Kiraly 對早期草稿的反饋。
概述
Gossipsub 新增的部分消息擴展功能,允許節點無需硬分叉即可升級到單元級傳播。這提升了本地內存池(getBlobs)數據的實用性。
這裡有一個 PR 草案,指定了共識客戶端如何使用部分消息擴展:通過部分消息規範添加單元傳播,作者:MarcoPolo · 拉取請求 #4558 · ethereum/consensus-specs · GitHub 。
介紹
Fusaka 在EIP-7594中引入了 PeerDAS。如 EIP 所述,糾刪碼數據塊以單元格列的形式傳播。這些列的類型為DataColumnSidecar 。這些列通過 gossipsub 傳播到網絡。如果某個節點已從其本地內存池中獲取某一列中所有引用的數據塊,則它可以自行獲取 DataColumnSidecar,而無需等待 Gossipsub 傳播。然後,它還會傳播該列以幫助快速傳播。Gossipsub 的 IDONTWANT 消息用於抑制來自已擁有 IDONTWANT 消息的對等節點的此類推送。
如果一個區塊的所有引用 blob 都出現在大多數節點的內存池中,那麼達到託管要求的速度會很快。數據已經存儲在本地。但是,即使只有一個 blob 缺失,節點也必須等待所有列在網絡中傳播完畢後才能達到託管要求。
理想情況下,我們只傳播給定行缺失的單元格。這樣節點就可以利用本地已有的數據。這就是 Gossipsub 的部分消息擴展所做的。
部分消息擴展允許節點簡潔地發送、請求和廣播單元。這是網絡層的一項改進,無需共識機制變更,可以向後兼容的方式部署,並且無需硬分叉。
部分消息擴展概述
完整的草案規範可以在libp2p/specs找到。
部分消息的行為與普通的 Gossipsub 消息類似。關鍵區別在於,部分消息不是通過哈希值引用,而是通過組 ID 引用。組 ID 由應用程序定義,並且必須能夠在無需完整消息的情況下推導出來。藉助組 ID 和位圖,節點可以有效地指定其缺少哪些部分以及可以提供哪些部分。
對於 PeerDAS,組 ID 即The Block塊根。它在每個主題中都是唯一的。完整的列構成一條完整的消息,而單元則是最小的部分消息。單元由子網主題(列索引)、組 ID(區塊根)以及位圖中的位置唯一且簡潔地標識。節點在收到The Block後即可開始廣播和請求單元。這是最快的速度,因為The Block聲明瞭包含的 blob。
使用常規 gossipsub 消息進行單元級傳播非常棘手。每個單元都必須通過其完整的消息 ID(20 字節)引用,並且節點無法預先請求缺失的單元;它們必須首先知道單元如何映射到其對應的消息 ID。相比之下,使用部分消息時,節點會通過位圖中的一位來引用每個單元,並且可以通過部分IHAVE
和部分IWANT
來交換它們能夠提供什麼以及它們需要什麼的信息。
可以將部分消息急切地推送給對等方,而無需等待對等方請求部分消息,或者可以根據請求提供部分消息。
Gossipsub 部分消息需要應用程序的配合,因為應用程序知道消息是如何組合和拆分的。應用程序負責:- 對可用部分和缺失部分進行編碼(例如位圖)。- 解碼部分請求,並使用已編碼的部分消息進行響應。- 驗證已編碼的部分消息。- 合併已編碼的部分消息。
假設示例流程
為了直觀地瞭解部分消息在實際中的工作原理,請考慮以下兩個示例。第一個例子使用即時推送來減少延遲,第二個例子則等待對方的請求,這可以減少重複消息,但會造成延遲(本例中 RTT 為 1/2)。
對於這兩個例子,假設:
- Peer P 是區塊的提議者。它知道區塊中的所有 blob。
- 對等體 A 和 B 是驗證者。
- The Block中的第一個 blob 之前並未公開分享給 A 和 B,因此他們都在本地內存池中丟失了這個 blob。
- P 連接到 A,A 連接到 B。P <=> A <=> B
急切推動
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐│ P │ │ A │ │ B │└──────────────────┘ └──────────────────┘ └──────────────────┘┌────────────────┐ │ ││Proposes Block B│ │ │└───────┬────────┘ │ ││ ┌──────────────┐ │ │├───│Forwards block│──▶│ ┌──────────────┐ ││ └──────────────┘ ├───│Forwards block│──▶││ ┌──────────────┐ │ └──────────────┘ ││ │ Eager push │ │ ┌──────────────┐ │├───│cell at idx=0 │──▶│ │ Eager push │ ││ └──────────────┘ ├──│cell at idx=0 │──▶ ││ │ └──────────────┘ ││ │ ││ │ ││ │ │▼ ▼ ▼
請求/響應
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐│ P │ │ A │ │ B │└──────────────────┘ └──────────────────┘ └──────────────────┘│ │ │┌───────┴────────┐ │ ││Proposes Block B│ │ │└───────┬────────┘ │ ││ ┌──────────────┐ │ │├───│Forwards block│──▶│ ┌──────────────┐ ││ └──────────────┘ ├───│Forwards block│──▶││ ┌──────────────┐ │ └──────────────┘ ││ │ IWANT │ │ ┌──────────────┐ ││◀──│ idx=0 │───│ │ IWANT │ ││ └──────────────┘ │◀──│ idx=0 │───││ ┌──────────────┐ │ └──────────────┘ ││ │ Respond with │ │ ┌──────────────┐ │├───│ cell@idx=0 │──▶│ │ Respond with │ ││ └──────────────┘ ├───│ cell@idx=0 │──▶││ │ └──────────────┘ │▼ ▼ ▼
請注意,對等體在知道對方擁有什麼數據之前會請求數據。該請求包含在對方的 IWANT 位圖中,該位圖與 IHAVE 位圖一起發送。這意味著,當對方能夠提供數據時,數據可以在一個 RTT 內接收。相比之下,一個“宣佈、請求、響應”流程需要 1.5 個 RTT。
出版策略
急切推送比請求/響應更快,但有發送重複信息的風險。
因此,出版策略應該是:
當確信這是該部分消息的第一次傳遞時,會急切地推動。
在具有私有 blob 的場景中,當節點收到私有 blob 時,以急切的推送進行轉發是合理的。
在具有分片內存池的場景中,節點急切地推送它知道對等節點由於其分片策略而不會推送的單元是合理的。
作為一種彈性機制,一些重複信息是預料之中的,也是必需的。可以通過調整即時推送的概率和向對等節點請求的概率來調整重複信息的數量。
然而,即使是一個簡單的策略,通過利用內存池中的本地數據,其性能也應該顯著優於我們目前的全列方法。重複的部分消息會導致重複的單元格,而不是重複的整列。
Devnet 概念驗證
目前有一個正在進行中的部分消息擴展的 Go 實現。此外,還有一個使用部分消息的Prysm 補丁。
為了驗證概念,我們創建了一個 Kurtosis 開發網絡,使用已修補的 Prysm 客戶端連接到已修補的 geth 客戶端(以便在 getBlobs 調用中返回部分 blob)。一些節點會使用“私有” blob 構建區塊。當這種情況發生時,其他節點會只請求缺失的“私有”單元,並使用其本地內存池中的 blob 填充列的其餘部分。
為了粗略地瞭解帶寬節省情況,我們還創建了一個較小的峰度網絡,該網絡僅包含兩個“超級”節點,用於接收和發送所有列。其中一個節點提議私有 blob。這意味著,當上述節點提議私有 blob 時, getBlobs 將無法為另一個節點返回完整的列。
我們測量了啟用和禁用部分消息時DataColumnSidecar
消息的發送數據。結果表明,啟用部分消息後,節點發送的數據量減少了 10 倍。原因有二:
- 當前 Prysm 的部分消息實現從不主動推送。節點會等待收到請求後再響應數據。
- 僅發送請求的部分。如果某個列只有一個私有 Blob,則我們只發送該 Blob。
在這種低延遲環境中(使用峰度在本地運行),IDONTWANT 並不有效。因此,我們認為,不急切地推送數據會帶來很大的好處。
然而,在存在私有 blob 的情況下,IDONTWANT 將不適用,因此這裡的性能優勢仍然代表更高延遲的更現實的環境。
圖表:
這只是兩個對等節點的實驗,但在包含更多對等節點的網格中,每次成對交互的行為應該相同。這是用於傳播數據列的帶寬。
內存池分片
內存池分片的未來方向只會使節點更有可能丟失一些單元,因此需要某種形式的單元級傳播。
基於“行”的傳播
請注意,如果某個節點在某一列的某個索引處缺失單元格,則它很可能在所有其他列的相同位置也缺失單元格。未來的一項改進可能是允許節點向其網格對等節點通告整行。這樣做的好處是,節點可以一次性填充該行所有缺失的單元格。缺點是存在出現較大重複消息的風險。
後續步驟Next steps
- 指定支持返回部分 blob 的 getBlobs V3 api。
- 在 Rust libp2p 中實現部分消息擴展。(正在進行中)
- 指定 DataColumnSidecars 的部分消息編碼。正在進行中
- 集成到 CL 客戶端。
- 在測試網上部署
- 在主網部署
- 縮放斑點計數