ネットワーキング・レイヤー
イーサリアムは、標準化されたプロトコルを使用して互いに通信できなければならない数千のノードを持つピア・ツー・ピアのネットワークです。「ネットワーキング・レイヤー」とは、これらのノードが互いを見つけ、情報を交換できるようにするプロトコルのスタックです。これには、ネットワーク上での情報の「ゴシップ」(1対多の通信)や、特定のノード間でのリクエストとレスポンスの交換(1対1の通信)が含まれます。各ノードは、正しい情報を送受信していることを保証するために、特定のネットワーキング・ルールに従う必要があります。
クライアント・ソフトウェアには2つの部分(実行クライアントとコンセンサス・クライアント)があり、それぞれが独自のネットワーキング・スタックを持っています。他のイーサリアムのノードと通信するだけでなく、実行クライアントとコンセンサス・クライアントは互いに通信する必要があります。このページでは、この通信を可能にするプロトコルについて入門的な説明を行います。
実行クライアントは、実行レイヤーのピア・ツー・ピア・ネットワーク上でトランザクションをゴシップします。これには、認証されたピア間の暗号化された通信が必要です。バリデータがブロックを提案(プロポーズ)するために選ばれると、ノードのローカルのトランザクション・プールからのトランザクションがローカルのRPC接続を介してコンセンサス・クライアントに渡され、ビーコン・ブロックにパッケージ化されます。その後、コンセンサス・クライアントはp2pネットワーク全体にビーコン・ブロックをゴシップします。これには2つの別々のp2pネットワークが必要です。1つはトランザクションのゴシップのために実行クライアントを接続するネットワーク、もう1つはブロックのゴシップのためにコンセンサス・クライアントを接続するネットワークです。
前提知識
このページを理解するには、イーサリアムのノードとクライアントに関するある程度の知識が役立ちます。
実行レイヤー
実行レイヤーのネットワーキング・プロトコルは、2つのスタックに分かれています。
-
ディスカバリー・スタック: UDP上に構築され、新しいノードが接続するピアを見つけることを可能にします。
-
DevP2Pスタック: TCP上に配置され、ノードが情報を交換することを可能にします。
両方のスタックは並行して機能します。ディスカバリー・スタックは新しいネットワーク参加者をネットワークに供給し、DevP2Pスタックはそれらの相互作用を可能にします。
ディスカバリー
ディスカバリーとは、ネットワーク内の他のノードを見つけるプロセスです。これは、少数のブートノード(クライアントにアドレスがハードコード (opens in a new tab)されており、すぐに見つけてクライアントをピアに接続できるノード)を使用してブートストラップされます。これらのブートノードは、新しいノードをピアのセットに紹介するためだけに存在します。これが唯一の目的であり、チェーンの同期などの通常のクライアント・タスクには参加せず、クライアントが最初に起動されたときにのみ使用されます。
ノードとブートノードの相互作用に使用されるプロトコルは、ノードのリストを共有するために分散ハッシュテーブル (opens in a new tab)を使用するKademlia (opens in a new tab)の修正版です。各ノードは、最も近いピアに接続するために必要な情報を含むこのテーブルのバージョンを持っています。この「近さ」は地理的なものではなく、距離はノードIDの類似性によって定義されます。各ノードのテーブルは、セキュリティ機能として定期的に更新されます。例えば、discv5 (opens in a new tab)では、ディスカバリー・プロトコルのノードはクライアントがサポートするサブプロトコルを表示する「広告」を送信することもでき、ピアが通信に使用できるプロトコルについて交渉できるようにします。
ディスカバリーはPING-PONGのやり取りから始まります。PING-PONGが成功すると、新しいノードがブートノードに「結合(bond)」されます。ネットワークに参加する新しいノードの存在をブートノードに知らせる最初のメッセージはPINGです。このPINGには、新しいノード、ブートノード、および有効期限のタイムスタンプに関するハッシュ化された情報が含まれています。ブートノードはPINGを受信し、PINGのハッシュを含むPONGを返します。PINGとPONGのハッシュが一致する場合、新しいノードとブートノード間の接続が検証され、それらは「結合された」と言われます。
結合されると、新しいノードはブートノードにFIND-NEIGHBOURSリクエストを送信できます。ブートノードから返されるデータには、新しいノードが接続できるピアのリストが含まれています。ノードが結合されていない場合、FIND-NEIGHBOURSリクエストは失敗するため、新しいノードはネットワークに参加できません。
新しいノードがブートノードから近隣ノードのリストを受け取ると、それぞれのノードとPING-PONGのやり取りを開始します。PING-PONGが成功すると、新しいノードとその近隣ノードが結合され、メッセージの交換が可能になります。
クライアントの起動 --> ブートノードへの接続 --> ブートノードとの結合 --> 近隣ノードの検索 --> 近隣ノードとの結合
実行クライアントは現在、Discv4 (opens in a new tab)ディスカバリー・プロトコルを使用しており、discv5 (opens in a new tab)プロトコルへの移行に向けた積極的な取り組みが行われています。
ENR: イーサリアム・ノード・レコード
イーサリアム・ノード・レコード(ENR)は、3つの基本要素を含むオブジェクトです。署名(合意されたアイデンティティ・スキームに従って作成されたレコード内容のハッシュ)、レコードの変更を追跡するシーケンス番号、および任意のキーと値のペアのリストです。これは、新しいピア間で識別情報の交換を容易にする将来を見据えたフォーマットであり、イーサリアムのノードにとって推奨されるネットワーク・アドレスのフォーマットです。
なぜディスカバリーはUDP上に構築されているのか?
UDPは、エラーチェック、失敗したパケットの再送、または接続の動的な開閉をサポートしていません。その代わり、正常に受信されたかどうかに関係なく、ターゲットに向けて情報の連続したストリームを発射するだけです。この最小限の機能は最小限のオーバーヘッドにもつながり、この種の接続を非常に高速にします。ノードがピアとの正式な接続を確立するために単に自身の存在を知らせたいだけのディスカバリーにおいては、UDPで十分です。しかし、ネットワーキング・スタックの残りの部分については、UDPは目的に適していません。ノード間の情報交換は非常に複雑であるため、再送やエラーチェックなどをサポートできる、より機能の充実したプロトコルが必要です。TCPに関連する追加のオーバーヘッドは、追加の機能に見合う価値があります。したがって、P2Pスタックの大部分はTCP上で動作します。
DevP2P
devp2p自体は、イーサリアムがピア・ツー・ピア・ネットワークを確立および維持するために実装するプロトコルのスタック全体です。新しいノードがネットワークに参加した後、それらの相互作用はdevp2p (opens in a new tab)スタック内のプロトコルによって管理されます。これらはすべてTCP上に配置され、RLPxトランスポート・プロトコル、ワイヤー・プロトコル、およびいくつかのサブプロトコルが含まれます。RLPx (opens in a new tab)は、ノード間のセッションの開始、認証、および維持を管理するプロトコルです。RLPxは、ノード間で送信するための最小限の構造にデータをエンコードする非常にスペース効率の良い方法であるRLP(Recursive Length Prefix)を使用してメッセージをエンコードします。
2つのノード間のRLPxセッションは、最初の暗号化ハンドシェイクから始まります。これには、ノードが認証メッセージを送信し、それがピアによって検証されることが含まれます。検証が成功すると、ピアは認証確認メッセージを生成して開始ノードに返します。これは、ノードがプライベートかつ安全に通信できるようにする鍵交換プロセスです。暗号化ハンドシェイクが成功すると、両方のノードが「ワイヤー上で」互いに「hello」メッセージを送信するトリガーとなります。ワイヤー・プロトコルは、helloメッセージの交換が成功することによって開始されます。
helloメッセージには以下が含まれます。
- プロトコルのバージョン
- クライアントID
- ポート
- ノードID
- サポートされているサブプロトコルのリスト
これは、両方のノード間で共有される機能を定義し、通信を構成するため、相互作用を成功させるために必要な情報です。各ノードがサポートするサブプロトコルのリストが比較され、両方のノードに共通するものがセッションで使用できるという、サブプロトコルのネゴシエーションのプロセスがあります。
helloメッセージとともに、ワイヤー・プロトコルは接続が閉じられることをピアに警告する「disconnect」メッセージを送信することもできます。ワイヤー・プロトコルには、セッションを開いたままにするために定期的に送信されるPINGおよびPONGメッセージも含まれています。したがって、RLPxとワイヤー・プロトコルの交換はノード間の通信の基盤を確立し、特定のサブプロトコルに従って有用な情報が交換されるための足場を提供します。
サブプロトコル
ワイヤー・プロトコル
ピアが接続され、RLPxセッションが開始されると、ワイヤー・プロトコルはピアの通信方法を定義します。当初、ワイヤー・プロトコルはチェーンの同期、ブロック伝播、およびトランザクション交換という3つの主要なタスクを定義していました。しかし、イーサリアムがプルーフ・オブ・ステーク(PoS)に移行すると、ブロック伝播とチェーンの同期はコンセンサス・レイヤーの一部になりました。トランザクション交換は依然として実行クライアントの管轄です。トランザクション交換とは、ブロック・ビルダーが次のブロックに含めるためにいくつかを選択できるように、ノード間で保留中のトランザクションを交換することを指します。これらのタスクに関する詳細情報はこちら (opens in a new tab)で入手できます。これらのサブプロトコルをサポートするクライアントは、JSON-RPCを介してそれらを公開します。
les(ライト・イーサリアム・サブプロトコル)
これは、ライト・クライアントを同期するための最小限のプロトコルです。従来、フルノードはインセンティブなしでライト・クライアントにデータを提供する必要があるため、このプロトコルはほとんど使用されてきませんでした。実行クライアントのデフォルトの動作は、lesを介してライト・クライアントのデータを提供しないことです。詳細情報はlesの仕様 (opens in a new tab)で入手できます。
Snap
snapプロトコル (opens in a new tab)は、ピアが最近の状態のスナップショットを交換できるようにするオプションの拡張機能であり、ピアが中間マークル・トライのノードをダウンロードすることなく、アカウントとストレージのデータを検証できるようにします。
Wit(ウィットネス・プロトコル)
ウィットネス・プロトコル (opens in a new tab)は、ピア間で状態のウィットネスを交換できるようにするオプションの拡張機能であり、クライアントをチェーンの先端に同期させるのに役立ちます。
Whisper
Whisperは、ブロックチェーンに情報を書き込むことなく、ピア間で安全なメッセージングを提供することを目的としたプロトコルでした。これはdevp2pワイヤー・プロトコルの一部でしたが、現在は非推奨となっています。同様の目的を持つ他の関連プロジェクト (opens in a new tab)が存在します。
コンセンサス・レイヤー
コンセンサス・クライアントは、異なる仕様を持つ別のピア・ツー・ピア・ネットワークに参加します。コンセンサス・クライアントは、ピアから新しいブロックを受信し、自分がブロック・プロポーザーになる番が来たときにそれらをブロードキャストできるように、ブロックのゴシップに参加する必要があります。実行レイヤーと同様に、これにはまず、ノードがピアを見つけ、ブロックやアテステーションなどを交換するための安全なセッションを確立できるようにするディスカバリー・プロトコルが必要です。
ディスカバリー
実行クライアントと同様に、コンセンサス・クライアントはピアを見つけるためにUDP上でdiscv5 (opens in a new tab)を使用します。コンセンサス・レイヤーのdiscv5の実装は、discv5をlibp2p (opens in a new tab)スタックに接続するアダプターを含み、devp2pを非推奨にしている点でのみ、実行クライアントの実装と異なります。実行レイヤーのRLPxセッションは、libp2pのNoiseセキュア・チャネル・ハンドシェイクを支持して非推奨となっています。
ENR
コンセンサス・ノードのENRには、ノードの公開鍵、IPアドレス、UDPおよびTCPポート、そして2つのコンセンサス固有のフィールド(アテステーション・サブネットのビットフィールドとeth2キー)が含まれています。前者は、ノードが特定のアテステーション・ゴシップのサブネットワークに参加しているピアを見つけやすくします。eth2キーには、ノードが使用しているイーサリアムのフォーク・バージョンに関する情報が含まれており、ピアが正しいイーサリアムに接続していることを保証します。
libp2p
libp2pスタックは、ディスカバリー後のすべての通信をサポートします。クライアントは、ENRで定義されているように、IPv4および/またはIPv6でダイヤルおよびリッスンできます。libp2pレイヤーのプロトコルは、ゴシップ・ドメインとリクエスト/レスポンス(req/resp)・ドメインに細分化できます。
ゴシップ
ゴシップ・ドメインには、ネットワーク全体に急速に広がる必要があるすべての情報が含まれます。これには、ビーコン・ブロック、証明、アテステーション、エグジット、およびスラッシングが含まれます。これはlibp2pのgossipsub v1を使用して送信され、送受信するゴシップ・ペイロードの最大サイズなど、各ノードにローカルに保存されているさまざまなメタデータに依存します。ゴシップ・ドメインに関する詳細情報はこちら (opens in a new tab)で入手できます。
リクエスト・レスポンス
リクエスト・レスポンス・ドメインには、クライアントがピアに特定の情報を要求するためのプロトコルが含まれています。例としては、特定のルート・ハッシュに一致する、またはスロットの範囲内にある特定のビーコン・ブロックを要求することなどが挙げられます。レスポンスは常に、Snappyで圧縮されたSSZエンコードのバイトとして返されます。
なぜコンセンサス・クライアントはRLPよりもSSZを好むのか?
SSZはSimple Serialization(シンプルなシリアライゼーション)の略です。固定オフセットを使用するため、構造全体をデコードすることなくエンコードされたメッセージの個々の部分を簡単にデコードできます。これは、エンコードされたメッセージから特定の情報を効率的に取得できるため、コンセンサス・クライアントにとって非常に便利です。また、マークル・プロトコルと統合するように特別に設計されており、マークル化(Merkleization)に関連する効率の向上が得られます。コンセンサス・レイヤーのすべてのハッシュはマークル・ルートであるため、これは大幅な改善につながります。SSZはまた、値の一意の表現を保証します。
実行クライアントとコンセンサス・クライアントの接続
コンセンサス・クライアントと実行クライアントは両方とも並行して実行されます。コンセンサス・クライアントが実行クライアントに指示を提供し、実行クライアントがビーコン・ブロックに含めるトランザクションのバンドルをコンセンサス・クライアントに渡すことができるように、これらは接続されている必要があります。2つのクライアント間の通信は、ローカルのRPC接続を使用して実現できます。「Engine-API」 (opens in a new tab)として知られるAPIは、2つのクライアント間で送信される指示を定義します。両方のクライアントは単一のネットワーク・アイデンティティの背後にあるため、各クライアント用の個別のキー(Eth1キーとEth2キー)を含むENR(イーサリアム・ノード・レコード)を共有します。
制御フローの概要を以下に示します。関連するネットワーキング・スタックは括弧内に記載されています。
コンセンサス・クライアントがブロック・プロデューサーではない場合:
- コンセンサス・クライアントは、ブロック・ゴシップ・プロトコルを介してブロックを受信します(コンセンサスp2p)。
- コンセンサス・クライアントはブロックを事前検証します。つまり、正しいメタデータを持つ有効な送信者から到着したことを確認します。
- ブロック内のトランザクションは、実行ペイロードとして実行レイヤーに送信されます(ローカルRPC接続)。
- 実行レイヤーはトランザクションを実行し、ブロック・ヘッダー内の状態を検証します(つまり、ハッシュが一致するかどうかを確認します)。
- 実行レイヤーは検証データをコンセンサス・レイヤーに返し、ブロックは検証されたと見なされます(ローカルRPC接続)。
- コンセンサス・レイヤーはブロックを自身のブロックチェーンの先頭に追加し、それに対してアテステーションを行い、ネットワーク全体にアテステーションをブロードキャストします(コンセンサスp2p)。
コンセンサス・クライアントがブロック・プロデューサーである場合:
- コンセンサス・クライアントは、自分が次のブロック・プロデューサーであるという通知を受け取ります(コンセンサスp2p)。
- コンセンサス・レイヤーは、実行クライアントの
create blockメソッドを呼び出します(ローカルRPC)。 - 実行レイヤーは、トランザクション・ゴシップ・プロトコルによって入力されたトランザクションのメンプールにアクセスします(実行p2p)。
- 実行クライアントはトランザクションをブロックにバンドルし、トランザクションを実行してブロック・ハッシュを生成します。
- コンセンサス・クライアントは、実行クライアントからトランザクションとブロック・ハッシュを取得し、それらをビーコン・ブロックに追加します(ローカルRPC)。
- コンセンサス・クライアントは、ブロック・ゴシップ・プロトコルを介してブロックをブロードキャストします(コンセンサスp2p)。
- 他のクライアントは、ブロック・ゴシップ・プロトコルを介して提案されたブロックを受信し、上記のように検証します(コンセンサスp2p)。
ブロックが十分な数のバリデータによってアテステーションされると、チェーンの先頭に追加され、ジャスティファイドされ、最終的にファイナライズ済みになります。
コンセンサス・クライアントと実行クライアントのネットワーク・レイヤーの概略図(ethresear.ch (opens in a new tab)より)
参考文献
DevP2P (opens in a new tab) LibP2p (opens in a new tab) コンセンサス・レイヤーのネットワーク仕様 (opens in a new tab) Kademliaからdiscv5へ (opens in a new tab) Kademliaの論文 (opens in a new tab) イーサリアムp2p入門 (opens in a new tab) Eth1/Eth2の関係 (opens in a new tab) マージとEth2クライアントの詳細動画 (opens in a new tab)

