Bitcoin Core 0.11 (ch 5): Initial Block Download
このページでは、ノードが最初にネットワークに参加するときにBitcoin Coreがブロックチェーンをダウンロードする方法について説明します。
背景編集
新しいノードがネットワークに参加すると、ブロックチェーン全体をダウンロードして検証します。これは、これを行うことによってのみ、ノードがすべてのトランザクションを独立して検証したと主張できるため、ビットコインの分散された性質にとって不可欠なステップです。
ブロックチェーンのサイズが大きくなるにつれて、コードが最適化されない限り、IBDに必要な時間が長くなります。 Satoshiの元のクライアントがリリースされて以来、さまざまな最適化が行われていますが、2014年には取引量が増加するため、平均的な接続を持つラップトップハードウェアの初期ダウンロードには最大24時間かかることがあります。開発者はこれが受け入れられず、「ヘッダーファースト」モードと呼ばれる新しいアプローチが開発されたことに同意した。このアプローチは、大幅なスピードアップをもたらしました。
"ヘッダーファースト"モード編集
「ヘッダーファースト」モードでは、新しいノードが最初にすべてのブロックヘッダーをダウンロードします。これは非常に小さく(約80バイトですが、ブロックは1MBまで可能です)。ノードが起点ブロックからブロックチェーンの現在の先端まで(2015年10月時点の380,000)、すべてのヘッダーを持つと、それだけで全ブロックのダウンロードが開始されます。
これでヘッダーが作成されたので、ノードは複数のピアから並列にブロックをダウンロードします。 (これはヘッダーが1つのピアからのみダウンロードされますが、ヘッダーが小さいのでそれほど重要ではありません)ノードは最大8つのピアから一度にダウンロードし、より速くピアに接続しようとすると、 。
ヘッダー優先IBDは、2014年に[1]で統合されました。
PRコメントに要約されているように、主な機能のいくつかは[コメントはここでわずかに編集されています]:
- 'getblocks'ではなく 'getheaders'を使用し、ヘッダーツリーを構築するために使用します。
- ブロックは、移動可能なウィンドウを使用して、使用可能なすべてのアウトバウンドピアから並行してフェッチされます。 1つのピアがウィンドウの動きを止めると、それは切断されます。
- 孤児のブロックはもうありません。まったく。ヘッダーを確認したブロックだけを要求し、すぐにディスクに格納します。つまり、ディスクフィル攻撃にはProof of Workが必要です。
- 最初のヘッダーの同期中は1に制限されますが、できる限り全員から同期します。
チェックポイント編集
Bitcoin Coreの初期ブロックダウンロードコードは、(単一のピアから)ダウンロードしているブロックヘッダが特定のハードコードされた「チェックポイント」を渡すことを確認します。
チェックポイントは、誰もが(Bitcoin Coreコミットアクセス開発者によって認識されたネットワーク参加者を意味する)すべての人が最長チェーン上にあると認識している長いブロックに対応するブロックハッシュです。チェックポイントは、それほど議論の余地のないほど大きく設定されています。
IBDチェックポイントチェックは、 main.cpp:2784 (バージョン0.11)にあります。
ハードコーディングされた数字はchainparams.cpp </u>にあります。 (0.11現在、13個のチェックポイントがあり、最初はブロック11,111、最後はブロック295,000)。 chainparams.cppから:
- / **
- *良いチェックポイントブロックを作るには?
- *ブロックは適度なタイムスタンプで囲まれています(前にタイムスタンプが付いていないブロックはありません。前にタイムスタンプがありません)
- *奇妙なトランザクションは含まれていません
- * /
IBDを実装するBitcoinコアコード編集
ほとんどのコードは main.h / cpp にあります。
'ブロックステータス'
ブロックステータスの概念は重要です(第2章および/または chain.cpp を参照)。ノードは、新しく受信したブロックにステータスを割り当て、ブロックに関する詳細を学習するとそのステータスを更新します。 (例えば、再編成があり、ブロックが最長のブロックチェーン上になくなった場合など、ブロックの状態も将来変更される可能性があります。)
'ブロックヘッダをダウンロードする'
コードは、単一のピアからヘッダをダウンロードすることから始まります。
ダウンロードは、メッセージ処理スレッドによって定期的に呼び出されるSendMessages( main.cpp )で開始されます。
コードが私たちが巻き込まれていないと判断し、誰とも同期を開始していない場合は、
- 私たちは送る: "getheaders"
- ピアの返信: "ヘッダー"(2000ヘッダーのチャンク)
- 私たちは送る: "getheaders"
- ピアの返信: "ヘッダー"(2000ヘッダーのチャンク)
- <continue ...>
- ピアの返信:「ヘッダー」(2000よりも少ないヘッダーのチャンク)
ピアが2000未満のヘッダーを送信すると、ピアのブロックチェーンの先端に到達したと判断されます。
'ブロックロケータ'
ブロックロケータは、ノードがブロックヘッダを交換し始めるべきである2つのノード間のフォークポイントを見つけるために使用されます。
primitives / block.h の定義を以下に示します。
/ *別のノードへのブロックチェーン内の場所を記述します。 *他のノードは同じブランチを持たず、最近の共通トランクを見つけることができます。 *さらに後ろに行くほど、フォークの前になることがあります。 * / struct CBlockLocator { std :: vector <uint256> vHave; ... (いくつかの基本的なコンストラクタと直列化メソッド) ... };
したがって、Locatorは基本的にブロックハッシュのベクトルです。このベクトルには、ピアとの共通ブロックを迅速に見つける可能性を最大にするように選択された32個のハッシュが埋め込まれている。 通常の操作では、私たちは通常、同僚の数ブロック以内にいると見込んでいます。したがって、ベクトルは指数関数的に「ジャンプバック」する前に、最後の10ブロックのハッシュで始まります。したがって、コメントは「もっと後ろにあるほど、フォークの前にはもっと先にある」という意味です。まず、あなたとあなたの同輩が互いに10ブロック以内にある場合、フォークポイントはハッシュあなたとあなたの同輩が互いに15ブロック離れているなら、あなたは確かにフォークポイントの数ブロック内にある共通のブロックを見つけて、フォークポイントを見つけるためにロケータのもう1回の反復だけが必要となります。しかし、あなたとあなたのピアが10,000ブロック分異なる場合、ロケータはフォークポイントの前に数百(またはそれ以上の)ブロックの共通トランクしか見つけられないので、別の2つまたは3つのロケータオブジェクトを0に交換する必要がありますフォークポイント。
CChain :: GetLocatorとCBlockIndex :: GetAncestorを参照してください(両方とも chain.cpp ])。
ブロック・スキップリスト・ポインター '
すべてのブロック索引には、次の属性が含まれます。
//このブロックのさらに先行するもののインデックスへのポインタ CBlockIndex * pskip;
与えられたブロックの "スキップポインタ"を選択するアルゴリズムは chain.cpp にあります:
//ジャンプする高さを決定します。厳密に高さよりも低い任意の数が許容可能であり、 //しかし、次の式はシミュレーションでうまくいくようです(最大110ステップ //最大2 ** 18ブロック)。 戻り値(高さ&1)? InvertLowestOne(InvertLowestOne(height-1))+ 1:InvertLowestOne(height) InvertLowestOne(int n){return nおよび(n-1); }
スキップポインタは、O(n)内のチェーンを歩くのではなく、O(log n)内のブロックチェーンを移動するために使用されます。
「スキップリスト」の背後にあるアイデアは、ここに記述されています:skiplistのためのWikipediaページ
'ブロックヘッダを処理する'
コードパス: ProcessMessage(エントリポイント) AcceptBlockHeader CheckBlockHeader(非コンテキスト検証チェック) ContextualCheckBlockHeader(ブロックチェイン認識検証チェック)
非コンテキストチェック
これらのチェックは、ブロックチェーンの状態を知る必要がないため、「非コンテキスト」です。 1)作業記録が要求事項を満たす:ここで、コードは、ブロックを構築するときに必要とされた鉱夫の請求権がPOWであることを確認します。後で、私たちのブロックチェーンに対して再チェックします。正直な鉱夫は、常に両方のチェックに合格する必要があります。必要な捕虜(block.nBits)について嘘をつく鉱夫だけがこのチェックに合格しますが、文脈のチェックには失敗します。
2)あまりにも遅いタイムスタンプ:コードは、タイムスタンプが将来2日以内であることをチェックします。これは、コードがブロックのタイムスタンプとシステム時刻を単に比較するため、コンテキストに依存しません。
コンテキストチェック
これらのチェックでは、ノードのブロックチェーンに関する知識が必要です。
1)作業証明の再確認:ここでは、(ブロックのnBitsに入れた難易度を信頼するのではなく)難易度目標に関する私たち自身の知識に基づいて、POWを再確認します。
2)チェックポイント(有効な場合):このブロックの高さがチェックポイントの場合、ブロックハッシュはチェックポイントマップに格納されているハッシュと一致します。 (checkpoint.cppを参照)。
3)タイムスタンプがあまりにも早くない:ここでは、ブロックのタイムスタンプが前のブロックのメジアン時間(前の11ブロック)よりも前でないことを確認する。これにより、メジアン時間がブロックごとに進み続けることが保証されます。明らかに、このチェックはアクティブチェーン内の先行ブロックの知識を必要とするためコンテキスト依存です。これはmapBlockIndexからブロックを取得することで得られます。詳細は、[BIP 113]を参照してください。
4)バージョン番号
ブロックを追加
最後に、ブロックをmapBlockIndexに追加し、pIndexBestHeaderを更新します(AddToBlockIndex()を参照)。
この時点で、ブロック索引はVALID_TREEです。主索引にあることはわかっていますが、まだトランザクションを受け取っていません。
'ブロックのダウンロード'
あなたのノードはすぐにヘッダーをダウンロードしているピアからブロックをダウンロードし始めます。
これは以下の理由によるものです。
- headers-peerはあなたが "version"メッセージ( main.cpp:4056 )を受け取ったときに設定された優先ダウンロードピアです。
- したがって、あなたのノードはあなたのヘッダーピア( main.cpp:5143 )のFindNextBlocksToDownloadを呼び出します。
ピアのヘッダーを受信したときにUpdateBlockAvailability()で設定されているので、あなたのヘッダーピアは有効なpIndexBestKnownBlock( main.cpp:421 u>)。
- FindNextBlocksToDownloadは、ブロックハッシュのベクトルを返します。ブロックハッシュのベクトルを返します( main.cpp:5145 [ブロックをリクエストベクトルに追加]および main.cpp:5178 を参照) > [あなたのヘッダピアにgetdata msgを送る])。
ただし、IBDが完了するまで、他のピアからブロックのダウンロードを開始しません。
これは以下の理由によるものです。
- あなたの他の同輩は、おそらくあなたのヘッダーピアと同じように、FindNextBlockToDownloadを呼び出すので、優先ダウンロードの同輩かもしれません。
- ただし、使用可能なブロックを見つけることができないため、その関数呼び出しはすぐに戻ります。
- IBDが終了すると(以下の説明を参照)、他の同僚とのヘッダーのやり取りが始まります。
- 他の同僚とヘッダーを交換したら、ブロックのリクエストを開始します。
ブロックは、各ピアから一度に16ブロックのチャンクでダウンロードされます。ブロックサイズが1MBの場合、ブロックがいっぱいになると、約16MBのチャンクが発生します。 (main.h - "MAX_BLOCKS_IN_TRANSIT_PER_PEER"を参照してください)。
「動いている窓」
ダウンロードコードは1024ブロックの「移動ウィンドウ」を使用します。アイデアは、あなたが複数のピアから異なるチャンクをダウンロードしているにもかかわらず、いつでもあなたがダウンロードしているブロックがかなり接近しているということです。これの主な目的は、ブロックチェーン上で互いに近くにあるブロックが、同じ.datファイル(生のブロックデータがディスクに格納される)に含まれる可能性が高いことです。ブロックチェインの場所とブロックファイルの場所との間に相関性を持たせることの利点の1つは、ノードが後でブロックデータを「プルーニング」することを選択した場合、古いブロックファイルを削除する方が簡単だということです。
ピアを切断する
ダウンロードを続行するために、コードは低速のピアを切断します。ピアは、2つのシナリオの下で切断することができます。
まず、ピアから要求されたブロックは、一定の時間枠内に配信されなければなりません。
通常、この時間枠は20分です。
次のコードは main.cpp にあります: GetBlockTimeout(...){return nTime + 500000 * consensusParams.nPowTargetSpacing *(4 + nValidatedQueuedBefore);}
nValidatedQueuedBeforeは、私たちに飛行しており、検証済みのブロックの数です。それをNと呼んでみましょう。
N = 0の場合、このコードは次のように評価されます。 = 500,000マイクロ秒(1/2秒)* 600秒(目標ブロック間隔)* 4 := 0.5秒* 600秒* 4 := 1,200秒 := 20分
数式は次のように簡略化できます。 := 0.5 *(4 + N)* block_interval :=(2 + 0.5 * N)* block_interval
従って: :N = 0、タイムアウト= 2 * block_interval = 20分。 :N = 10、タイムアウト= 7 * block_interval = 70分。 :N = 30、タイムアウト= 17 * block_interval = 170分。
ピアを切断する2番目の状況は、ピアが進行中のウィンドウが進行しないようにして、ダウンロード全体をストールすることです。移動するウィンドウが1000から2024のブロックで、1016から2024までのすべてをダウンロードしたとしますが、Aliceが1000から1016までサービスを提供するのを待っています。この中断で、アリスが移動中のウィンドウを2秒間ストールし続けると、アリスを落として彼女をより信頼できるピアに置き換えます。あなたの移動中のウィンドウが再び動き始めるように、あなたのノードはあなたの他のピアの1つからブロック1000〜1016を要求します
'ブロックを要求する'
リクエストブロックは、 "getdata"メッセージ(v0.11のmain.cpp:5137を参照)を送信することによって処理されます。 「getdata」メッセージは、「inv」メッセージのベクトルをパックし、グループとして送信するために使用されます。したがって、16の別個の "inv"メッセージを送信するのではなく、1つのネットワークメッセージでピアから16ブロックを要求できます。
どんなブロックを要求しますか?それを理解するコードはFindNextBlocksToDownloadにあります。 'ブロックロケータ' を使用して、このピアと共通している最後のブロックを特定し、そこから開始します。ブロックロケータは、ノードと所与のピアとの間のフォークポイントを効率的に見つける自家製アルゴリズムである。ロケータは、最後の10文字から始まり、通常はピアの10ブロック以内の定常状態にあるので、32ブロックのリストをパックし、次に指数関数的にジャンプします。 ( chain.cpp:25 )。
このアルゴリズムは、フォークポイントの特定に有効であることが証明されています。 "getdata"メッセージを準備したら、MarkBlocksAsInFlight(main.cpp)を使用して、リクエストしたブロックを「飛行中」としてマークします。
'受信ブロック'
ノードを受信するエントリポイントは、ノードがピアから「ブロック」メッセージを受信したときにProcessMessageにあります。
ブロックはProcessNewBlock(main.cpp)で処理されます。
:1)ブロックのチェック
- a)ブロックに関する基本的な、文脈に依存しないチェック(merkleルート、ブロックサイズなど)
- b)ブロック内のトランザクションで基本的でコンテキストに依存しない(トランザクションサイズなど)
- c)私たちがこのブロックを要求したことを確認します(要求されていなければ、これはDOS攻撃かもしれませんが、必ずしもそうではありません)
- d)ブロックのコンテキスト依存チェック
:2)ブロックをディスクに格納する
:3)ブロックチェーンの完全性の一般的なチェック(これは高価になるかもしれませんが、非常に高速です)
:4)ブロックをブロックチェーンに接続します - 必要に応じて(ActivateBestChainを参照)
ブロックがブロックチェーンの新しいチップとして接続されている最後のステップまで、ブロックのトランザクションは検証されません(二重消費などがチェックされます)。詳細は以下の「ブロックの接続」を参照してください。
ダウンロードの監視編集
RPCコールのgetpeerinfoを使ってダウンロードを監視することができます。これは(接続先の各ピアに対して)次のように表示されます。
- "synced_headers":このピアと共通する最後のヘッダー
- "同期ブロック":このピアとの通信の最後のブロック
- 「機内」:現在このピアからリクエストしているブロックの高さ