FABRKNT
P2P ネットワーキング内部 — devp2p からカスタム gossip まで
P2P ネットワーキング
レッスン 3 / 4·CONTENT18 分50 XP
コース
P2P ネットワーキング内部 — devp2p からカスタム gossip まで
レッスンの役割
CONTENT
順序
3 / 4

レッスン2 — カスタム gossip 構築(Reth 上の MEV-Boost 系メッセージング)

問い

Searcher ノードを走らせている。収益性の高い bundle を見つけた → 信頼できる builder の小集団にだけ送りたい。eth/68 transaction gossip では public 前提で「全員が全員を relay」モデル、「この特定の peer 集合にだけ送る」概念を持たない。そのギャップを埋めるカスタムサブプロトコルとは?

原理(最小モデル)

  • eth/68 の 3 前提(カスタムは全部壊す). Public(接続持つ誰でも見える)+ consensus 関連 + peer 全員 relay 協力。
  • カスタムが必要な 3 要素. Private gossip(特定 peer 集合のみ)+ アプリ層 routing(capability ベース)+ カスタム署名(chain 固有認証)。
  • 本番例 4 つ. MEV-Boost bundle(private orderflow)+ 共有 sequencer pre-confirmation + 決済レール merchant attestation + L2 sequencer 間 coordination。
  • bundle/1 プロトコル 4 メッセージ. Hello(handshake)+ BundleAnnounce(hash + 期待利益)+ BundleRequest(hash + 要求者署名)+ BundleData(暗号化 payload)。
  • Private プロトコルの discovery 3 パターン. Allowlist(peer ID ハードコード)+ Out-of-band 招待(Discord / GitHub で peer ID 配布、MEV-Boost)+ Tor routing(ネットワーク位置完全隠蔽)。
  • MEV-Boost の 4 役割対応. Relayer(売り)+ Builder(組み立て)+ Proposer(勝ち選択)+ Sealed-bid auction。
  • DOS 対策 4 軸. Announce spam(peer ごとレート制限 + 署名要求)+ 偽 bundle(要求時署名検証)+ 帯域食いつぶし(peer ごと上限 + eviction)+ Sybil(peer ID allowlist or PoS 紐付け)。

具体例

eth/68 の 3 前提とカスタムが破る部分:

eth/68 前提カスタムで壊す
Public(接続持つ誰でも見える)Private(特定 peer 集合のみ)
Consensus 関連アプリ層 routing
Peer 全員 relay 協力カスタム署名

bundle/1 プロトコルの 4 メッセージ:

メッセージ目的
HelloHandshake — サポート共有
BundleAnnounce「Hash X は bundle、~Y gas 抽出」
BundleRequest「Bundle X 送って」
BundleDataフル bundle(送信者向け暗号化)

メッセージ定義:

#[derive(Debug, RlpDecodable, RlpEncodable)]
pub enum BundleMessage {
    Hello { protocol_version: u8, peer_capabilities: u64 },
    BundleAnnounce { bundle_hash: B256, expected_profit_gwei: u64 },
    BundleRequest { bundle_hash: B256, requester_signature: Bytes },
    BundleData { bundle_hash: B256, encrypted_payload: Bytes },
}

ハンドラ実装:

use reth_network::SubProtocol;
use std::collections::HashMap;

pub struct BundleProtocol {
    known_bundles: HashMap<B256, Bundle>,
    peer_set: HashSet<PeerId>,
    signer: PrivateKeySigner,
}

impl SubProtocol for BundleProtocol {
    const NAME: &'static [u8] = b"bundle";
    const VERSION: u8 = 1;
    const MESSAGE_COUNT: u8 = 4;

    fn on_handshake(&mut self, peer: PeerId) -> eyre::Result<()> {
        // Peer が allowlist に含まれているかを認証
        if !self.peer_set.contains(&peer) {
            return Err(eyre!("not authorized peer"));
        }
        Ok(())
    }

    fn on_message(&mut self, peer: PeerId, msg: Bytes) -> eyre::Result<Option<Bytes>> {
        let parsed: BundleMessage = decode(&msg)?;

        match parsed {
            BundleMessage::Hello { protocol_version, .. } => {
                tracing::info!(peer = ?peer, version = protocol_version, "peer joined");
                Ok(None)
            }

            BundleMessage::BundleAnnounce { bundle_hash, expected_profit_gwei } => {
                // 手元に bundle がなければ要求する
                if !self.known_bundles.contains_key(&bundle_hash) {
                    let request = BundleMessage::BundleRequest {
                        bundle_hash,
                        requester_signature: self.signer.sign(&bundle_hash)?.to_bytes(),
                    };
                    Ok(Some(encode(&request)))
                } else {
                    Ok(None)
                }
            }

            BundleMessage::BundleRequest { bundle_hash, requester_signature } => {
                // 要求側の署名を検証
                verify_signature(&bundle_hash, &requester_signature)?;

                // Bundle を送信
                if let Some(bundle) = self.known_bundles.get(&bundle_hash) {
                    let encrypted = encrypt_for_peer(&peer, bundle.serialize());
                    let response = BundleMessage::BundleData {
                        bundle_hash,
                        encrypted_payload: encrypted,
                    };
                    Ok(Some(encode(&response)))
                } else {
                    Ok(None)
                }
            }

            BundleMessage::BundleData { bundle_hash, encrypted_payload } => {
                // 復号して保存
                let decrypted = decrypt_from_peer(&peer, encrypted_payload)?;
                let bundle = Bundle::deserialize(&decrypted)?;
                self.known_bundles.insert(bundle_hash, bundle);
                Ok(None)
            }
        }
    }
}

Reth への登録:

use reth_node_builder::NodeBuilder;

let bundle_protocol = BundleProtocol::new(allowlisted_peers, signer);

let node = NodeBuilder::new(config)
    .with_components(
        Components::default()
            .add_sub_protocol(bundle_protocol)
    )
    .launch()
    .await?;

Private プロトコルの discovery 3 パターン:

パターン仕組み
Allowlist ベース参加者 peer ID をハードコード内部 sequencer ネット
Out-of-band 招待別チャネル(Discord / GitHub)で peer ID 配布MEV-Boost
Tor routingネットワーク位置完全隠蔽高プライバシー chain

MEV-Boost の 4 役割対応:

概念実装
Relayer と builder の分離Relayer(売り窓口)+ Builder(組立て)+ Proposer(勝ち選択)
Sealed-bid auctionBuilder 入札、proposer が最高額選択
評判ベースBuilder 評判蓄積、relayer が不正参加者追跡
ネットワーク層 trust全て private p2p で完結、on-chain には乗らない

DOS 対策 4 軸:

攻撃Mitigation
アナウンス spamPeer ごとレート制限 + 署名付きアナウンス必須
偽 bundleAnnounce 時署名要求、要求時に検証
帯域食いつぶしPeer ごと帯域上限 + 超過で eviction
Sybil(偽 peer 大量)Peer ID allowlist or PoS 紐付け

失敗例(誤解)

「eth/68 で MEV bundle を gossip すれば良い」— 間違い。eth/68 は public 前提、bundle がネットワーク上の全員に漏れる → front-run される → searcher の収益消失。「private gossip」概念がない

「discv5 で private peer 集合を見つけられる」— 間違い。デフォルト discv5 は capability list を 丸ごとアナウンス → ネットワークスキャンする者が「このノードは bundle/1 サポート」と特定可能 → プライバシー目標台無し。Allowlist / Out-of-band / Tor のいずれか

「DOS 対策は後回しで良い」— 間違い。カスタムプロトコルは新たな攻撃面、デフォルト eth/68 の実戦防御がない。コアロジックと並行して保護を組み込む。最低 4 軸(spam / 偽 bundle / 帯域 / Sybil)を最初から。

🛑 予測。 Private な peer 集合に「MEV bundle を売りに出す」メッセージをブロードキャストしたい。なぜ eth/68 transaction を使わない? 別 gossip プロトコルを立ち上げる利点は?(答え: ① eth/68 は public 前提、bundle がネットワーク上の全員に漏れる → front-run される → searcher 収益消失、② eth/68 は「特定 peer 集合へ送る」概念がない、全員 relay モデル = 制御不能、③ eth/68 は consensus データ前提、アプリ層 routing(builder への入札、relayer の選択)が表現不能。カスタムサブプロトコル 3 利益 = ① Private(peer set 制御)+ ② Application-layer routing(capability ベース)+ ③ Custom 署名(chain 固有認証)。MEV-Boost / 共有 sequencer / 決済レール / 共有 sequencer coordination はすべてこのパターン。)

ステップで組み立てる

Step 1: eth/68 の 3 前提と破る部分

Public / Consensus / 全員 relay → Private / アプリ層 routing / カスタム署名。

Step 2: bundle/1 4 メッセージ

Hello / Announce / Request / Data。

Step 3: SubProtocol 4 必須要素

NAME / VERSION / MESSAGE_COUNT / on_message。

Step 4: Private discovery 3 パターン

Allowlist / Out-of-band(MEV-Boost)/ Tor。discv5 は丸ごとアナウンスで使えない

Step 5: MEV-Boost 4 役割の対応

Relayer / Builder / Proposer / Sealed-bid auction。

Step 6: DOS 対策 4 軸を最初から

Spam(レート + 署名)+ 偽 bundle(署名検証)+ 帯域(上限 + eviction)+ Sybil(allowlist / PoS)。

Step 7: chain 固有用途への当てはめ

Telos(intent matching)/ mppsol(決済 attestation)/ Hyperliquid(独自 transport)/ Tempo(決済 finality hint)。カスタム gossip は MEV / 決済 / sequencer coordination の自然な拡張点

答え合わせ

  • eth/68 がカスタムに合わない 3 理由: ① Public 前提(MEV bundle が漏れる)、② Consensus 関連メッセージ前提(アプリ層 routing 不可)、③ 全員 relay 協力前提(特定 peer 集合に送る概念なし)。カスタムサブプロトコルの 3 利益(Private / Application routing / Custom 署名)が eth/68 の 3 前提を反転。
  • discv5 が Private プロトコルに使えない理由: capability list を 丸ごとアナウンス → ネットワークスキャンする者が「このノードは bundle/1 サポート」と特定可能 → プライバシー目標台無し。代替は Allowlist(peer ID ハードコード)/ Out-of-band 招待(Discord / GitHub、MEV-Boost)/ Tor routing。
  • DOS 対策を最初から組む理由: カスタムプロトコルは新たな攻撃面、デフォルト eth/68 の実戦防御がない → 1 peer がコードを介して他 peer に flood 可能 = DOS amplifier 化。Coreロジックと並行して 4 軸(spam / 偽 bundle / 帯域 / Sybil)保護を組む。後回しは致命的

合格基準

  • eth/68 の 3 前提とカスタムが破る部分を言える。
  • bundle/1 の 4 メッセージを即答できる。
  • SubProtocol の 4 必須要素を言える。
  • Private discovery 3 パターン(Allowlist / Out-of-band / Tor)を例で言える。
  • DOS 対策 4 軸を即答できる。

まとめ(3行)

  • カスタム gossip = eth/68 の 3 前提(Public / Consensus / 全員 relay)を破る chain 固有メッセージング、3 利益(Private peer set + アプリ層 routing + カスタム署名)。
  • 実装は SubProtocol trait の 4 要素(NAME + VERSION + MESSAGE_COUNT + on_message)+ Reth NodeBuilder で 1 行登録、eth/68 と同 RLPx 接続上で並走。
  • 本番例 = MEV-Boost(Relayer + Builder + Proposer + Sealed-bid auction)+ 共有 sequencer coordination + 決済レール attestation、DOS 対策 4 軸(spam / 偽 / 帯域 / Sybil)を最初から組む。