Satoshi Client Block Exchange

提供: tezos-wiki
2018年4月13日 (金) 12:55時点における221.251.190.130 (トーク)による版 (Performance)
移動先: 案内検索

概要 この記事では、ノード間でどのようにブロックが交換されるかについて説明します。ブロックの検証方法の詳細については、「プロトコルルール」を参照してください。

初期接続時に、接続がインバウンド[1]でなかった場合、つまり接続がローカルノードによって開始された場合、バージョンメッセージはすぐに送信するためにキューに入れられます。リモートノードがバージョンメッセージを受信すると、それはそれ自身のバージョンメッセージで応答する[2]

ノードが「バージョン」メッセージを受信すると、ノードは「getblocks」要求をリモートノードに送信することができる。

ノードは、まだどのノードにも初期getblocks要求を送信していません。 または、これは唯一のアクティブノード接続です。おそらく、ノードはこの接続に先立って接続がゼロであったため、おそらく長時間切断されていた可能性があります。それで、それはブロックが追いつくように求めるでしょう。 getblocksメッセージは、遠隔ノートがノード間の最新の共通ブロックを見つけるのを助けるために、要求側ノードが既に所有している複数のブロックハッシュを含む。ハッシュのリストは、最新のブロックから始まり、10に戻り、起点ブロックに達するまで指数関数的に2倍になります。[3]両方のノードは起源ブロックでハードコードされているので、そこでの開始が最も少ないことが保証されています。そのブロックが何らかの理由で一致しない場合、ブロックは交換されません。

在庫メッセージ

getblocks要求を受信したノードは実際には送信しないことに注意してください 応答のフルブロック。ノードは、「inv」メッセージを送信します。 リクエストに合った一連のブロックのハッシュが検証されます ノードが実際にリモートノードが送信するブロックを送信していることを確認します 持っていない(しかし、遠隔ノードがフルブロックをまだ望んでいないと推測する)。

ローカルノードが「inv」メッセージを受信すると、ローカルノードは、 ブロックには "getdata"というメッセージがあります。下記参照。

しかし、まず、遠隔ノードがどのようにして "inv"メッセージを送信するかの詳細がここにあります ローカルノードによって送信された「getblocks」要求に応答して、リモート ノードは、pFrom-> PushInventoryを呼び出します。これは、 ブロックを要求したノードを表します(この中のローカルノード ウォークスルー)、PushInventoryはvInventoryToSendにブロックハッシュを追加します CNodeの変数。 main.cppのSendMessages関数は、 vInventoryToSendからアイテムを取り出し、それをvInv変数に追加します。 彼らが本当に送る準備ができていることを意味します。[4] 別個の変数の理由は、一部の在庫アイテム (今のトランザクションのみ)は、リモートノードに「流される」可能性があり、 それは彼らがすぐに送られないようにすることを意味します。 vInv変数が1000個のエントリでいっぱいになると、メッセージがキューに入れられます それらの1000のエントリで、ループが続行されます。最後に、 残りのエントリは最後の「inv」メッセージで送信されます。

ローカルノードが「inv」メッセージを受信すると、ローカルノードは、 "getdata"メッセージでブロックします。正確には、ノードはpfrom-> AskFor そのブロックを要求すると、そのメソッドはブロックの要求をキューに入れます mapAskFor、そして汎用目的のSendMessage()は "getdata"リクエストを送信します 地図からの1000のエントリのバッチで。[5]

コードでは、2分ごとに冗長な要求を制限しようとします。 メッセージを遅らせるmapAlreadyAskedForというマップを使用して同じブロック 必要に応じて[6]

ブロックバッチング

「getblocks」要求に対する応答ノードは、応答を制限しようとします 要求者に500ブロックまで。[7]

しかし、特有のひねりで、リクエスタが分岐したように見える場合 メインブランチから、ノードは必要なだけ多くのブロックを送信します 最後の共通ブロックから要求者のバッドチェーン全体を置換する ノード間で、リクエスタが持っている最後のブロックまで ブランチ)。 これは、メインブランチの500個のキャッチアップブロックに加えて 更新も送信されます。[8]

キューに入れられたブロック数のフラットな制限に加えて、 送信すると、ビットコインドはブロックされているブロックの合計サイズも制限します キューに入れられます。 これは現在、送信バッファサイズの半分[9]に制限されています。 10MB、送信のためにキューに入れられたブロックが5MBに制限されています。[10]

バッチ継続メカニズム

ノードがブロックインベントリのバッチを送信し終えると、バッチ内の最後のブロックのハッシュが記録されます。[11]ノードがそのフルブロックに対する要求を受信すると、ノードは現在のバッチで完了したことを認識し、最新のブロックハッシュを含む1つのブロックハッシュエントリを持つ特殊な "inv"メッセージを(通常のSendMessageメカニズムを迂回して) 12]遠隔ノードがその "inv"メッセージを受信すると、それはそのブロックを持たないことがわかり、上で説明したようにそのブロックを要求するでしょう。ただし、ブロックを受信して​​処理する今回は、前のブロックがないことに気付くため、最新のブロックを「孤立した」ブロックとして記録し、最新のブロックからブロックの更新を要求しますギャップを埋めるために孤児[13]の前にブロックまである。これは "getblocks"リクエストとして出され、バッチプロセス全体が繰り返されます。

しかし、ひねりがあります。次のバッチが終了すると、ブロックを送信するリモートノードは、通常のように最新のブロックハッシュを含む「inv」を送信し、ローカルノードは今回は孤立ブロックマップにこのブロックを既に持っていることに気付くので、ブロックを更新してブロックの更新を直接求める[14]このプロセスは、最新のブロックより前の最後のブロックが受信されるまで続きます。そのブロックの処理の最後に、このブロックを指している孤児が存在し、孤児ブロック(および他の孤児、再帰的に)を処理して、プロセス全体を完了することに気付くでしょう。

失速の回復

リモートノードが「バッチ継続メカニズム」を守らないなどの何らかの理由でバッチ処理が中断された場合、または切断が発生した場合は、プロセスを再起動する方法があります。 新しいブロックが解決され、[16]の周りに宣伝されると、後ろにあるノードは "inv"の新しいブロックに気づき、メッセージを送信したノードから "getblocks"更新を要求するようトリガーします。 これは、後ろにあるノードが現在あるブロックチェーンのどこからでもブロックが送信されるようにします。

ロングオーファンチェーン

さまざまなテストでは、ブロックチェーン上でかなり遅れているノードを検出することが比較的一般的であると分かっています(おそらく10人に1人以上)。これはおそらくキャッチアップの過程にあるためです。うまく接続されたノードには少なくとも8つの接続と最大で十数の接続があるため、新しいノードが追いついている別のノードに接続する可能性は非常に高いです。

追いついているノードは、メイン・チェーン内のブロックを他のすべてのノードに受け入れるので、処理中のブロックをアドバタイズします。[16]特定のチェックポイントの前に古いブロックを広告するのを防ぐコードがありますが、ブロックの高さがリモートノードの現在の最高の高さから2000ブロックを差し引いたものであれば、ブロックにはリモートノードにブロックを広告する節もあります。これは、たとえ両方が古いブロックを処理していても、ノードが他のノードに追いつくのを助けることを可能にするように見える。

これらの広告により、ローカル・ノードはリモート・ノードからこれらのブロックを要求します。これは、ローカルで処理されたものと比較して将来的にブロックされる可能性があります。ブロックが要求される方法のため、リモートノードは応答して大きなブロックのバッチを送信し、ブロックが最後に達するまでブロックをローカルノードに送信し続けます。これは、ローカルノードが別のノードからメインチェーン上の以前のブロックをダウンロードしているときに発生する可能性が高いことに注意してください。そのプロセスは最終的に孤立チェーンに追いつき、すべての孤立ブロックを再検証して接続する非常に長い操作を生成する可能性があります。 1万ブロック以上の孤立した鎖が処理に1時間以上かかる。

したがって、両方のノードが互いに話し合っている2つのノードは、特にどちらか一方が遠く、もう一方が他方よりはるかに遠い場合に、最適ではない対話につながる可能性があります。

洪水制限効果

上述のバッチ処理メカニズムがあっても、ブロックが交換されている間にリモートノードがローカル受信バッファをオーバーフローさせるというシナリオが発生します。

例えば、リモートノードが「追いついている」場合、特定の状況でローカルノードに処理する各ブロックをアドバタイズします(上記[17]を参照)。 ローカルノードは、それらのブロックのそれぞれを直ちに要求する。 これらのブロックをあまりにも多く要求しているローカルノードに対する保護はありません。 リモート・ノードは、要求されたすべてのブロックを送信します。 この状況では、ローカル・ノードが処理する時間が過ぎる前に、リモート・ノードが過度に多くのブロックを送信することに対する保護はありません。

ローカル受信バッファがいっぱいになる可能性があります。 ローカルノードが受信バッファがいっぱいであることに気付くと、そのノード接続は切断されます。[18] fDisconnectフラグをセットすると、バッファが空になったら[19]、ソケットは閉じます。

パフォーマンス

パフォーマンス 2011年9月1日現在、Comcastケーブルインターネット接続を使用してUbuntuを実行しているサーバークラスのコンピュータでは、ブロックチェーンをダウンロードして処理するのに10時間以上かかります。ダウンロードプロセスの初期段階でボトルネックがどのようなものかは議論の余地がありますが、最近のブロックの処理からは、ネットワークが最も遅いインターネット接続以外のボトルネックではないことは明らかです。

ブロックは、一度ダウンロードされると処理に平均して1秒以上かかる[20]しかし、ブロックの平均サイズは2011年8月にわずか24キロバイトです.24Kをダウンロードするのに1秒かかることはありません。また、テストでは、メッセージループごとに処理されるブロックの非常に大きなキューが明らかになります。スレッドがソケットに到着したときにキューからスレッドを引き出していた場合に期待されるものではありません。

問題がネットワークのパフォーマンスであると信じさせる多くの「偽の信号」があります。最初の誤った信号は、2011年8月現在、ダウンロードされたブロックの最初の60〜70%のほぼすべてが非常に小さいことです。最近の平均ブロックサイズは約100倍です!だから、突然、ほぼすべてのブロックレートは非常に高速から非常に遅くになります。何かが間違っているように見えます。現実には、ブロック処理の速度をキロバイト単位で測定すると、速度は比較的一定です。


もう1つの誤った信号は、ノードごとに1つずつメッセージキューが完了するまで処理されるという事実に関連しています。これは、他のノードからのメッセージの大きなバックアップをもたらす可能性があります。したがって、他のノードがサービスされるにつれて、長期間にわたるブロックの増加が長時間にわたってフリーズする可能性があります。ブロックダウンロードは、通常、1つのリモートノードから(少なくとも、鉱山者または他の中継またはダウンロードノードが遅延ブロックを宣伝してプロセスを中断するまで)、すべての作業が1つのノード上にあることを考えてください。ノードからブロックを処理することが速くなり、 "addr"メッセージが他のノードから処理され、他の作業が完了すると停止するように見えます。しかし何かが間違っているように見えます。

また、上記の孤立効果は、孤立チェーンが接続されるまで、何も表示せずに過剰なブロック処理につながる可能性があります。また、おそらく処理ブロックであるか、マシンや接続が遅いために、応答が遅いノードに陥ることもあります。

上記のすべてが、ブロックダウンロードプロセスでの「ジッタ」に大きく寄与します。これは、一定のダウンロード速度よりもユーザーの不満を感じます。

Footnotes

1. See pfrom->fInbound where pfrom is a CNode.

2. See ProcessMessage() in main.cpp where strCommand == "version".

3. See CBlockLocator in main.h.

4. See Message: inventory in SendMessage in main.cpp.

5. See Message: getdata at the end of SendMessage in main.cpp.

6. See CNode::AskFor in net.h.

7. See ProcessMessage() in main.cpp where strCommand =="getblocks".

8. See

int nLimit = 500 + locator.GetDistanceBack();

in ProcessMessage in main.cpp where strCommand =="getblocks".

9. See

if (--nLimit <= 0 || nBytes >= SendBufferSize()/2)

in ProcessMessage() in main.cpp where strCommand =="getblocks".

10. See

inline unsigned int SendBufferSize() {
       return 1000*GetArg("-maxsendbuffer", 10*1000); }

in net.h.

11. See pfrom->hashContinue = pindex->GetBlockHash();

   in ProcessMessage() in main.cpp where strCommand =="getblocks".

12. See: if (inv.hash == pfrom->hashContinue)

   in ProcessMessage() in main.cpp where strCommand =="getdata".

13. See:

       // Ask this guy to fill in what we're missing
       if (pfrom)
           pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2));

in ProcessBlock() in main.cpp.

14. See:

       else if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash))
           pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(mapOrphanBlocks[inv.hash]));

in ProcessMessage() in main.cpp where strCommand =="inv".

15. See:

   // Recursively process any orphan blocks that depended on this one

in ProcessBlock() in main.cpp.

16. See the last block of code in AcceptBlock in main.cpp.

17. See:

       if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : 134444))
   in AcceptBlock() in main.cpp.

18. See:

if (nPos > ReceiveBufferSize()) {

in ThreadSocketHandler2() in net.cpp.

19. See:

       if (pnode->fDisconnect ||
           (pnode->GetRefCount() <= 0 && pnode->vRecv.empty() && pnode->vSend.empty()))

in ThreadSocketHandler2() in net.cpp.

20. This is from the authors experience and also

   see https://bitcointalk.org/index.php?topic=31376.0.