仕様把握
ということで bitcoin wiki を見る。
headers メッセージは、getheaders メッセージへの応答として、ブロックヘッダをまとめて送る。
このメッセージが返すブロックヘッダーはなぜかトランザクション数を含んでいる。(通常ブロックのハッシュ値を計算するときに使うブロックヘッダはトランザクション数を含まない)そして、トランザクション数は常に0になる。なぜトランザクション数のフィールドを含む必要があるのかな。
サマリ
Headersメッセージを受け取ったときの処理内容のサマリです。
1. 受け取ったヘッダが0個だったら正常終了
2. ブロックアナウンスとして、今のActive Chainとつながらないブロックを受け取った時
受け取ったヘッダがすでにノードが持っているブロックとつながらない、かつ、ブロックの数が(MAX_BLOCKS_TO_ANNOUNCE)8より小さい場合
これがあまりに多いと不正なノードとしてマークする
接続ノードの UnconnectingHeaders をインクリメントする
この値が MAX_UNCONNECTING_HEADERS (10) を超えたらbanscore に20のペナルティが加えられる。
デフォルトのBANSCOREのしきい値は100.
UnconnectingHeaders は1度正常なHEADERSメッセージを受け取るとリセットされる。つまり10回続けてActiveChainにつながらないヘッダーリストを送ってきた場合は、不正な挙動とみなす。このあとの処理でGETHEADERSメッセージで、自ノードのチェーンとつながるブロックヘッダを要求しているので、正常なノードなら、この次に送ってくるHEADERSメッセージは、チェーンにつながるブロックになるはず。そうでない場合は、適当なデータを送ってきているDoS目的の悪意のあるノードである可能性が高い。
相手からブロックヘッダを取得する。
自身のactivechain につながるヘッダーをGETHEADERS メッセージを送り要求する。
相手ノードのステータスを更新する
受け取った一番最後のブロックのハッシュを、相手ノードの最新ブロックとして記録する
正常終了する
3. 受け取ったブロックヘッダのリストが、連なる一連のチェーンとなっていることを確認する。
0番目のハッシュ値が次のブロックの prevblockhash と一致するかをリストの最後までチェックする。
チェックに失敗すれば相手ノードのbanscoreにペナルティを加えて異常終了する。
4. まだ持っていない新しいヘッダを受け取ったかどうかの判定をする
受け取った中で一番新しいヘッダを、自身が持っているか確認をする。持っていない場合は新しいヘッダを受け取ったと判定する。
5. 受け取ったブロックヘッダを取り込む処理
失敗したら banscore にペナルティを加える
6. 相手ノードのステータスをリセット、更新する
UnconnectingHeaders を 0 にする
相手ノードの BlockAvailability(持っているサイン心のブロックを記録する項目)を 更新する
このブロックヘッダーの取り込みでactive chain が更新されたら、ノードの m_last_block_announcement を現在時刻で更新する
7. 相手ノードがまだ未知のブロックを持っていそうな場合、GETHEADERSメッセージを送って要求する
受け取ったヘッダの数が MAX_HEADERS_RESULTS と同じ場合(つまり、上限いっぱいのブロックヘッダを送ってきている場合)、未知のブロックを持っているとみなす。
8. ダイレクトフェッチ
受け取ったブロックヘッダを正常に取り込み終え、取り込んだチェーンが、自身のActiveChainよりもChainWorkが多い(≒長い)時にダイレクトフェッチを実施する。ダイレクトフェッチでは、取り込んだヘッダに対応するブロック全体をGETDATAメッセージによって取得する。
9. IBDの場合、相手ノードが同期済みでなければ接続を切る
相手ノードが持っているチェーンが minimumChainWork に満たない場合は接続を切る
minimumChainWork はチェーンごとのCChainParamsに定義されている。これよりもChainWorkが小さいということは、ブロックチェーンの同期ができていないことが明らかであるため、ここからのブロックのダウンロードをやめるために接続を切る。
10. 優良なアウトバウンドピアへの接続を保持する
最大4つのノードまで設定できるプロテクト対象に入れることで、切断から保護する。
5. 受け取ったブロックヘッダを取り込む処理
1. 受け取ったブロックヘッダのリストから1個ずつ以下の処理を実行する
BlockheaderがGenesisブロックの場合
次のヘッダの処理へ進む
既知のブロックヘッダの場合
過去の検証で不正となっていれば処理を失敗とし、以降のブロックヘッダの処理も中止する
そうでなければ次のヘッダの処理へ進む
PoWを検証する(CheckBlockHeader)
成功すれば処理を続行
失敗すれば以降のブロックヘッダの処理も中止する
1つ前のブロックが既知のブロックに含まれない、または、前のブロックが過去の検証で不正となっている場合
処理を失敗とし、以降のブロックヘッダの処理も中止する
ブロックヘッダの各フィールド bits, timestamp, version とチェックポイント前での分岐がないかを検証する(ContextualCheckBlockHeader)
成功すれば処理を続行
失敗すれば以降のブロックヘッダの処理も中止する
一つ前のブロックが、先祖ブロックに不正なブロックを持っている場合、その不正な先祖ブロックまでたどりながら、途中のすべてのブロックについて 不正なブロック(BLOCK_FAILED_CHILD)であるとマークする
2. ActiveChainの先端のヘッダが変わったことを通知する。
読んでいく
以下がメッセージを受け取ったときの処理ですが、ここではパラメータのロードだけで、実際の処理は ProcessHeadersMessage() へ移譲しているようです。
code:cpp
if (strCommand == NetMsgType::HEADERS)
{
// Ignore headers received while importing
if (fImporting || fReindex) {
LogPrint(BCLog::NET, "Unexpected headers message received from peer %d\n", pfrom->GetId());
return true;
}
std::vector<CBlockHeader> headers;
// Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks.
unsigned int nCount = ReadCompactSize(vRecv);
if (nCount > MAX_HEADERS_RESULTS) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 20, strprintf("headers message size = %u", nCount));
return false;
}
headers.resize(nCount);
for (unsigned int n = 0; n < nCount; n++) {
ReadCompactSize(vRecv); // ignore tx count; assume it is 0.
}
return ProcessHeadersMessage(pfrom, connman, headers, chainparams, /*via_compact_block=*/false);
}
200行近くあって、なかなかのボリュームですね。手応えがありそうです。ひとまずざっくりどういうことをやっているのか把握したいです。
まず、受け取ったヘッダの数が0だったら何もせずに終了していますね。
code:cpp
const CNetMsgMaker msgMaker(pfrom->GetSendVersion());
size_t nCount = headers.size();
if (nCount == 0) {
// Nothing interesting. Stop asking this peers for more headers.
return true;
}
次はこのコードですが、ここではノードが知っているブロックの中から、headersメッセージで受け取った一番ブロック高が若いブロックの一つ前のブロックを探し、見つからなければ、ピアの追跡情報を更新して、処理を終了しています。
code:cpp
// If this looks like it could be a block announcement (nCount <
// MAX_BLOCKS_TO_ANNOUNCE), use special logic for handling headers that
// don't connect:
// - Send a getheaders message in response to try to connect the chain.
// - The peer can send up to MAX_UNCONNECTING_HEADERS in a row that
// don't connect before giving DoS points
// - Once a headers message is received that is valid and does connect,
// nUnconnectingHeaders gets reset back to 0.
if (!LookupBlockIndex(headers0.hashPrevBlock) && nCount < MAX_BLOCKS_TO_ANNOUNCE) { nodestate->nUnconnectingHeaders++;
connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), uint256()));
LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n",
headers0.GetHash().ToString(), headers0.hashPrevBlock.ToString(), pindexBestHeader->nHeight,
pfrom->GetId(), nodestate->nUnconnectingHeaders);
// Set hashLastUnknownBlock for this peer, so that if we
// eventually get the headers - even from a different peer -
// we can use this peer to download.
UpdateBlockAvailability(pfrom->GetId(), headers.back().GetHash());
if (nodestate->nUnconnectingHeaders % MAX_UNCONNECTING_HEADERS == 0) {
Misbehaving(pfrom->GetId(), 20);
}
return true;
}
次に、一番ブロック高が大きい(新しい)ブロックのハッシュ値を hashLastBlock に入れています。この過程で、headersメッセージで受け取ったブロックが連続して(チェインして)いない場合は、不正として処理を中断しエラーにします。
code:cpp
uint256 hashLastBlock;
for (const CBlockHeader& header : headers) {
if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) {
Misbehaving(pfrom->GetId(), 20, "non-continuous headers sequence");
return false;
}
hashLastBlock = header.GetHash();
}
hashLastBlockがまだ知らないブロックであれば、新しく作られたブロックを受け取ったとして、received_new_headrフラグを立てています。
code:cpp
// If we don't have the last header, then they'll have given us
// something new (if these headers are valid).
if (!LookupBlockIndex(hashLastBlock)) {
received_new_header = true;
}
この中の ProcessNewBlockHeaders() が重要な役割を果たしているようです。この中で、受け取ったヘッダを1つずつ検証し、問題がなければ BlockIndex ..
code:cpp
CValidationState state;
CBlockHeader first_invalid_header;
if (!ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast, &first_invalid_header)) {
if (state.IsInvalid()) {
MaybePunishNode(pfrom->GetId(), state, via_compact_block, "invalid header received");
return false;
}
}