Satoshi Client Block Exchange

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

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

初期接続時に、接続がインバウンド[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]、ソケットは閉じます。

Performance

As of September 1, 2011, on a server class computer circa 2005 running Ubuntu with a Comcast cable internet connection takes over 10 hours to download and process the block chain. While it is debatable what the bottleneck is early in the download process, it is clear from the processing of recent blocks that the network is not the bottleneck for all but the slowest internet connections.

Blocks are taking over a second, on average, to process once downloaded.[20] However, the average size of a block is only around 24 kilobytes in August 2011. It certainly does not take 1 second to download 24K. Also, testing reveals very large queues of blocks being processed per message loop, which is not what you would expect if the thread was pulling them out of the queue as they arrive on the sockets.

There are a number of "false signals" that lead one to believe the problem is with network performance. The first false signal is that, as of August 2011, nearly all of the first 60 or 70% of blocks downloaded are very small. Recent average block sizes are around one hundred times bigger! So, almost all of a sudden, the block rate goes from very fast to very slow. It looks like something went wrong. In reality, if you measure the rate of block processing by kilobyte, the rate remains relatively constant.


Another false signal is related to the fact that message queues are processed to completion, one at a time per node. This can result in big backups of messages from other nodes. So, a long period of increasing blocks may freeze for long periods as other nodes are serviced. Consider that block downloads typically come from just one remote node (at least until a miner or other relaying or downloading node advertises a late block and disrupts the process) and so all the work might be on one node. Things go fast processing the blocks from a node, and then that looks like it stops as "addr" messages are processed from other nodes and other work is done. But it looks like something is wrong.

Also, the orphaning effects described above can lead to excessive block processing with nothing to show for it until the orphan chain is connected. Also, you do ocassionally run into a node that is slow to respond, perhaps because they are also processing blocks or because they have a slow machine or connection.

All of the above contributes to heavy "jitter" in the block download process, and that is a more frustrating user experience than a constant download rate.


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.