Bitcoin Core 0.11 (ch 4): P2P Network
Bitcoinはピアツーピアネットワークであるため、Bitcoin Coreにはピアを検出してそれらの接続を管理するコードがあります。
ネットワーク処理コードのほとんどは、 net.h / cpp </ u>にあります。
目次
ピアを管理するためのデータ構造[編集]
任意の所与の時間に、ノードは他のノードのセット、すなわちピアに接続される。 デフォルトでは、コードは8つのアウトバウンドピア(私たちのノードが出て見つけたノード)に接続し、最大125のインバウンドピア(ネットワーク経由で私たちを見つけるノード)を許可します。
グローバル変数vNodes(<u> net.h </ u>)は、ピアの集合を保持します。 変数はcs_vNodesによって保護されています。
各ピアはCNodeで表されます。
CNodeには数多くの属性が含まれており、そのほとんどは低水準の配管(ソケット、バイトストリームなど)と関係しています。
CNodeの主な属性の一部は次のとおりです。
属性 | 説明 |
---|---|
nServices | 一般に「サービスビット」と呼ばれます。" これは、ピアが提供するサービスのビットマップです。 0.11から、これはまだバイナリです。ノードはNODE_NETWORK(完全ノードであり、すべてです)か、そうでない(SPVです)。 I将来のバージョンでは、これらのビットは、ノードが何をすることができるかできないかについてより正確な情報を伝えるでしょう。 たとえば、ブロックプルーニングを使用すると、ノードは最近のブロック(たとえば、最後の1週間または2ブロックのブロック)を処理できますが、ブロックチェーン全体は処理できません。 |
fClient | このピアがSPVノードであるかどうか。 (ここで、「クライアント」という用語は「完全なノードではなく単にSPV」を意味します) |
fOneShot | |
fInbound | このノードが「インバウンド」か「アウトバウンド」か。 一般的な意味では、ネットワークを介して発見したノード(アウトバウンドノード)は、私たちを見つけたノードよりも攻撃する可能性が低くなります。 したがって、たとえば、履歴ブロックを要求するピアを探すコードでは、可能であればアウトバウンドノードが優先されます。 |
fWhiteListed | ホワイトリストに登録されたノードは、悪い行為を禁止されていません。 |
setInventoryKnown | |
vSendMsg | ピアに送信するためにキューに入れたメッセージ。 これは<CSerializeData>タイプです。ネットワーク経由で送信するためです。 |
vRecvMsg | ピアから受け取ったメッセージ。 これは<CNetMessage>型です。データが受信されるとすぐに、デシリアライズされ、より有用な形式(オブジェクト)にパックされるためです。 |
ピア発見と接続[編集]
アドレスマネージャー[編集]
ノードのピアのIPアドレスはアドレスマネージャーによって管理されます(<u> addrman.h </ u>を参照)。
コードコメントはアドレスマネージャーについて説明しています(ここでは簡潔に編集しています):
設計目標:
*アドレステーブルをメモリ内に保持し、テーブル全体をpeers.datに非同期でダンプします。
*(ローカライズされた)攻撃者がテーブル全体を自分のノード/アドレスで埋めることができないことを確認してください。
目的:
*アドレスはバケットにまとめられています。
*まだ試されていないアドレスは1024個の新しいバケットに入ります。
アクセス可能であることが知られているノードのアドレスは、256個の「試した」バケットに入れられます。
*バケットの選択は、ランダムに生成された256ビットの鍵を使用する暗号化ハッシュに基づいています。これは、敵が観測できないようにする必要があります。
*高性能のためにいくつかのインデックスが保持されます。 DEBUG_ADDRMANを定義すると、データ構造全体の頻繁な(そして高価な)一貫性チェックが導入されます。
「タイムスタンプ」
アドレスマネージャーはまた、各ピアが最後にいつ聞いたかを追跡します。タイムスタンプはアドレスでのみ更新され、タイムスタンプが20分以上経過するとデータベースに保存されます。タイムスタンプの役割を理解することで、タイムスタンプが、アドレスが発見されたさまざまな方法ごとに保持される理由をより明確にすることができます。
ピア発見[編集]
プログラムは、いくつかの異なる方法でノードのIPアドレスとポートを検出します。
- アドレスデータベース(peers.dat)
- ユーザー指定(-addnodeおよび-connect)
- DNSシード
- ハードコードされた種子
- 他のピアから( "getaddr"と "addr"メッセージ)
'1)アドレスデータベース(peers.dat)'
ノードは、スタートアップ時に読み込まれ、アドレスマネージャにロードされるデータベース(peers.dat)にアドレスを格納します。
この方法は、ビットコムネットワーク上の他のノードについてはまだ認識していないため、プログラムの初回実行時には機能しません。
コードがデータベースに保存される時期と方法の詳細については、上記のアドレスマネージャのセクションを参照してください。
'2)コマンドラインでユーザー指定'
ユーザーは、-addnode <ip>または-connect <ip>を使用して、コマンドラインで接続するノードを指定できます。
ユーザー指定のIPアドレスに関する注記:
- 複数のノードを指定することができます。
- アドレスは、最初はゼロのタイムスタンプが与えられているため、 "getaddr"要求に応じてアドバタイズされません。
- -connectを使用すると、IPアドレスはpeers.datに追加されず、指定されたアドレスだけが使用されます。
- -addnodeを指定すると、指定されたアドレスが開始点として使用されますが、すぐに他のピアを学習します。
3)DNSシード '
DNSシードは、peers.datデータベースが空で(プログラムを最初に実行しているときのように)、ユーザーが-addnodeまたは-connectを持つノードを指定していない場合にのみ使用されます。
この場合、ノードは他のピアのIPアドレスを検出するためにDNS要求を発行できます。
0.11から、6つのDNSサーバーがプログラムにハードコードされています - chainparams.cppを参照してください。
DNS応答には、要求された名前の複数のIPアドレスを含めることができます。
DNSを介して発見されたアドレスは、最初に "getaddr"要求に応答して広告されるのを避けるために、ゼロのタイムスタンプが与えられます。
'4)ハードコーディングされたノード(最後の手段)'
DNSシードが失敗した場合、クライアントにはビットコインノードを表すハードコードされたIPアドレスが含まれます。参照:<u> chainparamsseeds.h </ u>
これらのアドレスは最後の手段としてのみ使用され、ログメッセージが表示されます。「固定シードノードをDNSとして追加することはできないようです」 (<u> net.cpp </ u>)
アイデアは、これらのノードの過負荷を避けるために、できるだけ早くシードノードから離れることです。
ローカルノードに十分なアドレスがあると(おそらくシードノードから学習された)、接続スレッドはシードノード接続を閉じます。
DNSシードアドレスと同様に、ハードコードされたシードアドレスには、「getaddr」要求に応答して広告されるのを避けるためにゼロタイムスタンプが与えられます。
'5)他のノード( "getaddr"と "addr"メッセージ)から'
ノードは、 "getaddr"と "addr"メッセージを使ってIPアドレスを他のノードと交換します。
通常、「addr」メッセージは「getaddr」に応答して送信されます。
しかし、ノードが以下のような場合にアドレスを無償でアドバタイズするため、「addr」メッセージも迷子にならないことがあります。
- 中継アドレス(下記参照)
- 自分のアドレスを定期的に広告します。 (24時間ごとに、ノードは接続されているすべてのノードに自分のアドレスを通知します)。
- 接続が行われると(最初の「バージョン」メッセージに応答して)
ノードが「getaddr」メッセージを送信するのはいつですか?
- アウトバウンドノードからの「バージョン」メッセージに応答して、まだ1000のアドレスがない場合。
"addr"メッセージを受け取る:
- 送信ノードが古いバージョンで、既に1000のアドレスがある場合は無視されます。
- 送信側ノードが現在のバージョンで、1000を超えるアドレスを送信しようとしている場合、ピアは誤動作のために処罰されます。
- アドレスが過去24時間に表示され、タイムスタンプが現在60分以上経過している場合は、60分前に更新されます。
- アドレスが過去24時間に表示されず、タイムスタンプが現在24時間以上経過している場合は、24時間前に更新されます。
"getaddr"メッセージへの応答:
- ノードは、過去3時間のタイムスタンプを持つアドレスの数を計算します。
- これらのアドレスは送信されますが、2500を超えるアドレスがある場合はランダムに2500が選択されます。
- リモートノードが持っていると思われるアドレスのリストをクリアします。これにより、ノードへの送信がリフレッシュされます。 SendMessagesを参照してください。
アドレスリレー
一旦追加されると、次の条件が満たされれば、新たに受信されたIPアドレスを他のノードに中継することができる。
- アドレスタイムスタンプは最近のものです(現在の時刻から10分以内)
- "addr"メッセージには10個以下のアドレスが含まれています
送信側ピアの* fGetAddr = false。詳細については、コードを参照してください。
- アドレスはルーティング可能でなければなりません。
- コード:
if(addr.nTime> nSince &&!pfrom-> fGetAddr && vAddr.size()<= 10 && addr.IsRoutable())
- このテストに合格すると、コードの次のステップが実行されます(詳細はmain.cppを参照)。
//決定的なランダム性を使用して同じノードに一度に24時間送信し、選択されたノードのaddrKnownsが繰り返しを防止するようにします。
ピア接続性[編集]
接続スレッド(ThreadOpenConnections)は、使用可能なアドレスを選択して接続を行い、必要に応じてノードを切断します。
アウトバウンド接続のためのCSemaphoreの使用 '
このコードでは、発信接続の数を管理するためにセマフォを使用します(通常は8)。
セマフォを扱うコードのほとんどは、<u> net.cpp </ u>にあります。
接続が開かれると、セマフォグラントがCNodeデータ構造に渡されます。これは、ソケットスレッドがセマフォを解放することを可能にします。 pnode-> grantOutbound.Release()// net.cppを参照してください。
コードCSemaphore grant(* semOutbound)は、利用可能な接続があるまで待機します。
'インバウンド接続:受け入れと切断'
インバウンド接続は合計125まで可能です。
ThreadSocketHandlerには、インバウンド接続を受け入れるコードがあります。
ソケットスレッドループ:
- 1)fDisconnectフラグがセットされているソケットを切断します(空のバッファを持っています)
- 2)すべてのソケットを "select"
- 3) "select"を呼び出します。これは、一連のソケット上での活動を待つシステムコールです。
select()呼び出しが返ってくると、ノードは新しい接続を受け取り、準備完了のソケットを受信して送信し、切断する非アクティブなソケットをインバウンドまたはアウトバウンドのいずれかにマークします。 次の場合、ソケットは切断されます。 * 60秒前で、データを送受信していません。 *過去20分間にデータを送受信していない(TIMEOUT_INTERVAL = 20 * 60)(ピアが古いバージョンの場合は90分)
*ソケットがバッファをオーバーフィルします(CNode :: ReceiveMsgBytes-からの大量のメッセージ、<u> net.cpp </ u>の\ n "を切断してください)
ソケットとメッセージ[編集]
'ソケットスレッド'
ソケットスレッドはTCP層で動作します。
それは無限ループを経て、ソケットを読み書きします。 (<u> net.cpp </ u>を参照してください)。
ループには3つの基本的なアクティビティが含まれます。
1)管理作業:未使用のソケットの切断、どのソケットにデータがあるかの確認、新しい接続用のソケットの追加。
2)受信データ:recv()システムコールを使用してデータを持つソケットを読み取り、そのデータをピアのCNetMessagesのキューに入れます。 CNetMessageは、データをメッセージヘッダーとメッセージデータ(vRecv)の2つのデータストリームに編成します。ソケットスレッドは、この特定のピアからのすべてのメッセージを処理するまで、バッファを読み取ります。
3)送信データ:メッセージスレッドはvSendMsgオブジェクトとして送信メッセージをキューイングしたので、ソケットスレッドはこれらのオブジェクトを逆シリアル化してsend()を使用して送信します。 (send()はシステムコールであり、異なるオペレーティングシステム間の非互換性は<u> compat.h </ u>ファイルで処理されます)。
'メッセージスレッド'
これがプログラムの主なスレッドです。
主に「ビジネスロジック」レベルで動作します。トランザクションの検証、ブロックチェーンの管理などです。
ある意味では、ノードのすべてのアクティビティは、インバウンド・メッセージを処理するか、アウトバウンド・メッセージを準備するという形をとります。
ソケットスレッドと同様に、このスレッドは、インバウンドメッセージを処理し、アウトバウンドメッセージをキューイングするwhile(真)ループで構成されます。プログラムの初期化が完了すると、このループ(<u> net.cpp </ u>:ThreadMessageHandlerを参照)がプログラムの上位レベルの制御点になります。
ループはシグナルを使用して、<u> main.cpp </ u>に処理待ちのメッセージがあることを通知します。シグナルはProcessMessages()によって取得されます。
シグナルの使用はマルチスレッドとは関係ありません。信号は同じスレッドで送信され、取得されます。シグナルの使用は、<u> main.cpp </ u>から<u> net.cpp </ u>を切り離す目的でバージョン0.9で導入されました。バージョン0.8では、ループは単純にProcessMessages()関数を呼び出しました。シグナルに変更することで、<u> net.cpp </ u>コードは処理コードを意識する必要がなくなりました。その依存関係を削除すると、<u> main.cpp </ u>は<u> net.h </ u>の知識を必要とするため、循環インクルードを避けることができます。
プルリクエストの導入信号はPR 2154です。
"main.h"依存関係を削除するコミットは、[1]です。
'ProcessMessages(<u> main.cpp </ u>)'
ProcessMessages()は、トランザクションやブロックなどを処理して検証するほとんどすべてのコードの<u> main.cpp </ u>のエントリポイントです。
vRecvストリームでメッセージ開始シグネチャを検索しようとします。メッセージの開始を検出すると、開始前にすべてを削除します。次に、ヘッダーを読み取り、メッセージの種類を抽出し、メッセージのProcessMessageを呼び出します。
ProcessMessage()は、基本的には、扱うメッセージのタイプに基づいてアクションを実行する大きな「スイッチ」です。
多くの場合、メッセージを処理する過程で、コードはメッセージを送信キューにプッシュします。例えば、着信する「getdata」メッセージを処理するとき、ノードは発信データを待ち行列にプッシュする。
'SendMessages(<u> main.cpp </ u>)'
SendMessages()はメッセージを作成し、ピアのvSendMsgキュー(ダブルエンドキュー、またはC ++の "deque")にキューイングします。 vSendMsgオブジェクトは、基本的にはシリアル化されたデータです。
SendMessagesは、行うべき作業を探しているさまざまなデータ構造を通ります。メッセージを生成すると、アウトバウンドデータをキューに入れるCNode-> PushMessageを呼び出します。 (コードにメッセージを生成し、CNode-> PushMessageを呼び出す他の多くの場所があることに注意してください; SendMessages()には、送信キューにメッセージを配置する際に排他的なライセンスはありません)
データがPushMessageによってキューに入れられると、ソケットスレッドが来るのを待って待機します。
ソケットスレッドとメッセージスレッドは、ピア固有のロック(node-> cs_vSend)を使用してソケットへのアクセスを調整します。
ロック[編集]
コードのP2Pの側面に関連する主なロックは次のとおりです。
- cs_vNodesは、CNodeオブジェクトへのアクセスを制御します。
- cs_vSendは、ノードの送信バッファへのアクセスを制御します。
- cs_vRecvMsgは、ノードの受信バッファへのアクセスを制御します。
- cs_inventory
サービス拒否防止[編集]
DoS防止は、誤動作している同輩を追跡し、それらが間違っている場合、それらを禁止することによって実装されます。
DoS防止フレームワークは、2011年にPull 517で導入されました。
要約すると、
大きなアイデア:ピアが明らかに間違った情報を送っている場合は、接続を中止してそれを罰し、そのIPアドレスを禁止してすぐに再接続できないようにします。
接続を切断する確率と禁止の長さは、間違った方法と、潜在的に無駄な/有害な方法によって異なります。したがって、追加の 'バージョン'メッセージを送信することは、通常許容される軽微な違反であり、MAX_BLOCK_SIZE以上のブロックを送信することは大きな犯罪です。
具体的な例として、「私が期待していなかったバージョンメッセージがあります」を使用して、詳細なハウツーワークを行います。
ピアからのバージョンメッセージを取得すると、ピアの「不正行為」スコアが10増加し、(ピアの最初の悪い動作であると仮定すると)切断される確率は10%になります。接続が切断されている場合、そのピアのIPアドレスは数時間接続が禁止されます。接続が切断されていない場合、ピアが再び誤動作しない限り何も起こりません。そうであれば、切断される機会が増え、禁止される時間が長くなります。
誤解/禁止情報はメモリ内にのみ保存され、誤動作しているピアに関する情報はブロードキャストされません。また、切断/禁止されているピアは、ただドロップされ、警告や理由が送信されません。
- ギャビン・アンドレセン
</ blockquote>
出典:https://github.com/bitcoin/bitcoin/pull/517 </ blockquote>
'禁止されたノード'禁止されたノードのセットは、<u> net.cpp </ u>でsetBannedされています。
デフォルトでは、ノードは24時間禁止されていますが、これは-bantimeオプションで設定できます。
関連項目[編集]
Core 0.11(Ch 1):概要
Category:開発者
Bitcoin Core 0.11(Ch 2):データストレージ
Core 0.11(Ch 3):初期化とスタートアップ
Bitcoin Core 0.11(Ch 5):初期ブロックダウンロード
Bitcoin Core 0.11(Ch 6):ブロックチェーン