Bitcoin Core 0.11 (ch 6): The Blockchain

提供: tezos-wiki
移動先: 案内検索


このページでは、ブロックチェーンを管理するコードについて説明します。


ブロックインデックスとブロックステータス

ブロックインデックスデータベースは、ノードの起動時にメモリにロードされます。 これは、アクティブチェーンだけでなくブロックツリー全体を意味します。 src / txdb.cpp のLoadBlockIndexGuts()を参照してください。

ブロックインデックス(ブロックメタデータ)はコード内によくコメントされています。 src / chain.h を参照してください。

ブロックの重要な特徴の1つは、「検証ステータス」です。

検証ステータスは、コードがこのブロックとその祖先ブロックを検証した程度を取得します。

ブロックのステータスは次のいずれかです。

  • VALID_HEADER = 1
  • VALID_TREE = 2
  • VALID_TRANSACTIONS = 3
  • VALID_CHAIN = 4
  • VALID_SCRIPTS = 5

各ステータスコードの正確な意味は、 chain.h に記載されています。

また、ブロック索引には、次の2つの変数があります。 : 'nTx:' このブロック内のトランザクションの数。 :nTx> 0は、ブロックが少なくともVALID_TRANSACTIONSのステータスを持つことを意味します。

'nChainTx:' このブロックのチェーンまでの、このブロックまでのトランザクションの数。 :このブロックのトランザクションとそのすべての親が利用可能である場合にのみ、この値が設定されます。 :したがって、nChainTx> 0は、VALID_TRANSACTIONSであるチェーンの短縮形です。この情報はblock-status enumで利用できないため、注目に値する。つまり、VALID_TRANSACTIONSはその親がTREEであることを意味し、VALID_CHAINは親がCHAINであることを意味します。ある意味では、式(nChainTx!= 0)は、VALID_TRANSACTIONS以上VALID_CHAIN未満であるため、「VALID_nChainTx = 3.5」と言われる可能性があるステータスを省略したものです。 :メモ:nChainTxはメモリにのみ保存されます。データベースに対応するエントリがありません。

キー変数

まず、クイックC ++のリマインダ:

  • map:順序付けされていない<key、value>コンテナ(ハッシュテーブルと考える)
  • set:順序付けられた<key>コンテナ(ソートされたリンクリストと考える)
  • multimap:重複キーが許される順序マップ<key、value>(したがって、<a、1>、<b、2>、<b、3>の要素を保持できる)


'mapBlockIndex(map <block_hash、CBlockIndex *>)'

このマップには、すべての既知のブロック(「ブロック」は「ブロックインデックス」を意味します)が含まれています。ブロックインデックスは、ヘッダが受信されたときに作成され、LevelDBに格納されるため、フルブロックをまだ受信せずにブロックマップにブロックインデックスを持つことができます。

mapBlockIndexはソートされません。ちょうどあなたのブロック/ LevelDBと考えてください。キーはブロックハッシュです。

これは技術的にBlockMap型ですが、読みやすくするためです。 BlockMapは、unordered_map <ブロックハッシュ、ブロックインデックス*、comparator_function>です。

mapBlockIndexは、起動のステップ7で実行されるLoadBlockIndexGutsのデータベースから初期化されます。その後、新しいブロックがネットワーク上で受信されるたびに更新されます。

mapBlockIndexは拡大するだけで、縮むことはありません。 (main.cppでmapBlockIndex.eraseを検索してみてください)また、ブロックインデックスのLevelDBラッパーには、データベースからブロックを消去する機能が含まれていないことに注意してください - 書き込み関数(WriteBatchSync)はデータベースに書き込むだけです。比較すると、チェーンステートラッパーの書き込み関数(BatchWrite)は書き込みと消去の両方を行います。 ( txdb.cpp を参照してください)。


'mapBlocksUnlinked'

"すべてのペアA-> Bを含むマルチマップ。ここでA(またはその祖先であれば1つはトランザクションに失敗しますが、Bにはトランザクションがあります)" ( main.cpp:125 のコメント)。

mapBlocksUnlinkedの目的は、欠落した中間ブロックを受け取ったときに、すでに受信したブロックをブロックチェーンにすばやく添付することです。

代替方法は、mapBlockIndex全体を検索することです。 ただし、リンクされていないブロックを別のデータ構造で追跡する方が効率的です。

   例1:

 Aをヒントにしよう。

   B、Cのブロックヘッダーを受け取ります。    我々はCの完全なブロックを受け取る。    mapBlocksUnlinked = <B、C>


ブロックBを受信すると、Cを接続できます。

   例2:

 Aをヒントにしよう。

   B、C、Dのブロックヘッダーを受け取ります。    私たちはDのフルブロックを受け取ります。    mapBlocksUnlinked = <B、C>、<B、D>、<C、D>

ブロックBを受け取った時点で、Bをヒントとして接続し、mapBlocksUnlinkedのエントリを削除することができます。これは、<C、D>という1つの項目で構成されます。

setBlockIndexCandidates '

現在のヒントよりも総作業量が多いブロックインデックスのセット。 (ブロックが現在のヒントを拡張している通常の場合、ヒントよりも総作業量が多いことが分かります)。したがって、現在のブロックチェーンを拡張するための "候補"です 候補者がいるチェーンへの連鎖)ヘッダーを受け取ったときではなく、ブロックを受け取る前に、ブロックの証明実績を検証するので、これを「候補者」と呼びます。 したがって、「ヘッダー」はチェーンを拡張する候補ですが、フルブロックを受け取るまで確実には言えません(候補が現在のヒントから1ブロック以上離れている場合は、 すべての中間ブロックを受信して検証します)。

    例1:Aをヒントにしよう。 B、C、Dの順番で次のように受け取ります。

   A -- B
     \
      C -- D

私たちはB、C、Dのヘッダーを検証し、それらはすべて見栄えが良いです。この時点で、setBlockIndexCandidatesには<B、C、D>が含まれています。 BはCよりも仕事が多いがDより仕事は少ないと仮定する。

今度はBのフルブロックを受け取ってチェックアウトします。この時点で、chainActiveをBという新しいヒントとして拡張し、setBlockIndexCandidatesからBを削除します。 Bよりも仕事量が少ないのでCを削除しますが、Dはまだあります。

今度はCとDを受け取ります。Cは有効ですが、Dは悪い取引(二重支払い、無効な署名など)を持っています:

  • Cをディスクに保存し、mapBlocksIndexに保持する - これは既知のブロック
  • Dを破棄(ディスクに保存しないでください)し、mapBlocksIndexから削除します - これは不良ブロックです
  • setBlockIndexCandidatesからDを削除する

今、chainActiveはGenesis - - - - - - A - Bであり、setBlockIndexCandidatesは<empty>です。


'pindexBestHeader'

この変数は、あなたのノードが検証した最良の(ほとんどの作業)ヘッダーを保持します。

最初にブロックインデックスをロードするときに設定され、mapBlockIndexに現在のpIndexBestHeaderよりも多くの作業を持つヘッダーを追加するときに更新されます。

コンテキスト依存およびコンテキスト依存のチェックを通過すると、ヘッダーがmapBlockIndexに追加されます。


'chainActive(ベクトル<CBlockIndex *>'

chainActiveは聖なるブロックチェーンです。これはブロックの線形セットであり、最も長いチェーンを構成するブロックのうち、Genesisブロックから始まり、頂点に達するブロックのみから構成されています。

chainActiveはCChainであり、いくつかの便利なメソッド( chain.h を参照)で束ねられたブロックインデックスのベクトルです。

起動時に、チェーンはActivateBestChain( main.cpp )を呼び出すステップ10( init.cpp )で初期化されます。


'pindexBestHeaderとchainActive.Tipの違い'

pindexBestHeaderとchainActive.Tipは必ずしも同じものではないことに注意することが重要です。

pindexBestHeaderは、トランザクションを受け取る前に、私たちのノードが受け取った最も仕事のあるヘッダへのポインタです。これとは対照的に、chainActive.Tipは、ブロック全体を受信して​​検証した後にのみ設定されます。

ブロックの接続

ノードは、アクティブなブロックチェーンの先端よりも多くの作業証明を持つブロックを受信して​​受け入れると、このブロックを新しいチップにしようとします。

ノードのアクティブチェーンチップがブロックB1である「定常状態」では、典型的な事象は、10分おきにノードがB1の上に構築された新しいブロックB2を受け取り、受け入れることである(B2 "prev_block"はB1のハッシュです)、B2がB1よりも多くの作業を行い、B2をB1に接続することにより、アクティブチェーンが拡張されます。

ConnectBlock()は、「二重支出」が発見される重要な瞬間です。ブロックのトランザクションは、現在のUTXOセット(コインデータベース)に対して検証されます。具体的には、ConnectBlockのコードは各トランザクションをループし、トランザクションのすべての入力が実際にコインデータベースに格納されていることを確認します。 (コインが前のブロックに費やされていた場合、それは現在のUTXOセットにならないではないでしょう。それはUTXOセットの異なる、以前のビューであったが、もはやだろう。)ConnectBlockコードはまた、いくつかのDoS保護が含まれていますコード:例えば、トランザクションが過剰な数の署名操作(高価です)を含まないことを保証します。

ConnectBlock()はProcessNewBlockの最後のステップです。したがって、ブロックが受け入れられてディスクに格納された後にのみ発生します。アイデアは抽象的でブロックが有効であるが、そのトランザクションが有効かどうかは、ブロックがチェーンに追加される特定のポイントで設定されたUTXOに依存するということです。これは、定常状態であっても、後の時点までは分からないことがある。現在の先端がB1であると仮定すると、B2とB3によって急速に延長されます。何らかの理由で、私たちはB2の前にB3を受け取ります(おそらくB2は世界中の鉱夫によって作成されましたが、B3はB2を作った鉱夫とのよりよいリンクを持つ隣人によって採掘されました)。 B3を受け取った瞬間、B2で作成されただけなので、コイン・データベースに入力がない取引が含まれている可能性があります(おそらくそうです)。いずれにしても、基本チェックを超えてB3を検証する努力は行われません。その後、B2を受け取ると、それを接続してUTXOセットを更新し、次にB3がB2を拡張することに気付きます。 B3のトランザクションは検証され、チェックアウトされると仮定すると、アクティブチェーンの新しいヒントになります。

ブロック全体がチェックアウトされると、ブロックが接続され、取り消しデータが作成されてディスクに格納されます。

ブロックのトランザクションの1つに問題がある場合、ブロックは接続されず、コードはバッドブロックを送信したピアを処罰します。 (InvalidBlockFoundを参照してください)。

ブロックの切断(再編成)

再編成(re-org)は、ノードがchainActive.Tipから派生しないより長いチェーンが存在することを認識すると発生します。

ほとんどの再編成はワンブロック再編成のみです。これは競合する鉱夫がほぼ同時に新しいブロック(AとBと呼ぶ)を見つけるたびに発生する可能性が高い。ネットワークのいくつかは、最初にA、Bを受け取ります。最終的に、新しいブロックCがAまたはBの上にマイニングされます。これは、Cを生産した鉱夫がAかBのチェーンで働いたかどうかによって決まります。 CがAの上でマイニングされていると仮定すると、Bをブロックチェーンの先端として受け入れたノードは、Bを切り離し、代わりにAとCを付ける必要があります。

コード内でreorgがどのように起こるかは次のとおりです。

  • 新しいブロックを受け取ったとき、チェーンのヒントよりも多くの作業がある場合は、setBlockIndexCandidatesに追加します(main.cpp:ReceivedBlockTransactionsを参照)
  • ブロックが処理された後、ActivateBestChainは、現在のチェーンのヒントよりも多くの作業をしているブロック(たとえば、処理したばかりのブロックなど)があるかどうかを確認します。
  • ActivateBestChainStepは必要に応じてブロックを切断して新しいチップに到達します。明らかに、通常の場合、ブロックを切断する必要はありません。


'DisconnectTip()は以下を行います:'

  • このブロックで消費されたutxoをutxoセット(DisconnectBlock)に返します。
  • ブロックのトランザクションをmempoolに返します。 (今は別のブロックで採掘する必要があります;接続しようとしているブロックの1つにある可能性が高い、その場合は再び削除します)
  • chainActive.Tipを1ブロック戻します。
  • ウォレットを更新します。

コードがブロックをフォークポイントに戻すと、コードはフォークポイントから新しいチップにブロックを接続します。

'DisconnectBlock(utxoをutxoセットに戻す)'

このコードでは、undo.datファイルを使用して、切断されたブロックで費やされたコインを「消去」します。

  • ディスクから元に戻すファイルを読み込みます。
  • 逆順でトランザクションを処理する(CreateNewBlockはゼロのconfトランザクションを "順番に"作成するため、逆順でなければなりません。つまり、ブロックにT1とT2があり、T2がT1コインを消費する場合、T1はトランザクションベクトルのT2より前に来なければなりません。したがって、コインを使い果たしたとき、T2はT1の前に使われなくてはならない)。
  • 使用済みコインを見上げる
  • コインをヘルパー関数に費やす。ApplyTxInUndo:coins-> vout [out.n] = undo.txout;


関連項目

Core 0.11(Ch 1):概要
Bitcoin Core 0.11(Ch 2):データストレージ
Core 0.11(Ch 3):初期化とスタートアップ
Bitcoin Core 0.11(Ch 4):P2Pネットワーク
Bitcoin Core 0.11(Ch 5):初期ブロックダウンロード


Category:技術 Category:開発者