受け取るときのコードを読んだので、送るときのコードを読んでいく。
送る処理はいろいろなところで行われているので、コアとなるロジックを調べていく
Locator
Locator は相手にどのヘッダを要求するか伝えるためのパラメータで、ブロックハッシュのリストをメッセージに含めて送る。この処理を見てみる。
処理の概要
この関数では、引数に渡されたブロックの1つ前から順に後ろにたどりながらブロックハッシュを集めていきます。10個集めると、それ以降は間隔をそれぞれ2倍にしながら、ジェネシスブロックに達するまでブロックハッシュを集めます。
なぜこの仕様になったのかの考察
getheaders メッセージで、どのブロックを出発地点にしたいのかが相手に伝われば良いので、一見して一つのブロックハッシュを送れば十分なように見えます。
しかしそれでは不十分です。そのブロックハッシュが自身が知らないブロックであった場合、自分のtipが伸びた先のブロックなのか、過去のどこかでブランチしたチェーンのブロックなのかわかりません。
過去のどこかでブランチしたチェーンなのであれば、自分が知っているより長いチェーンを教えてあげることで同期する必要があります。この場合のやり取りの様子を図に書きました↓。locatorの選択などは簡略化してあります。
https://gyazo.com/0114da43effc8165bb4b3dfc4731118b
こんな感じで自分が持っている、相手が知らないより長いチェーンのブロックを送ることが出来ます。
こういった理由でlocatorではブロックハッシュを一つではなくリストで送っているのだと思います。
コードを見る
code:cpp
CBlockLocator CChain::GetLocator(const CBlockIndex *pindex) const {
int nStep = 1;
std::vector<uint256> vHave;
vHave.reserve(32);
if (!pindex)
pindex = Tip();
while (pindex) {
vHave.push_back(pindex->GetBlockHash());
// Stop when we have added the genesis block.
if (pindex->nHeight == 0)
break;
// Exponentially larger steps back, plus the genesis block.
int nHeight = std::max(pindex->nHeight - nStep, 0);
if (Contains(pindex)) {
// Use O(1) CChain index if possible.
} else {
// Otherwise, use O(log n) skiplist.
pindex = pindex->GetAncestor(nHeight);
}
if (vHave.size() > 10)
nStep *= 2;
}
return CBlockLocator(vHave);
}
引数
pindex は CBlockIndex のポインタですね。 const なので、単純に計算のための引数として受け取っているようです。CBlockIndex はブロックの情報に加えて、前後のブロックへのポインターやブロック高など便利なフィールドを追加したブロッククラスです。Core では実質ブロックを指すものとして使われています。
pindexを指定しなかった場合は Tip (アクティブチェーンの先端ブロック)を指定するようですね。
vHave
uint256のベクターです。uint256 は 256 bit の符号なし整数で、256bit 長のハッシュ値を保持する型としてよく使われます。ここでもブロックハッシュのために使っています。
この変数に返り値となるブロックハッシュのデータを追加していき、ロケータのデータを作っていくようです。
名前が vHave担っているのは、vはベクターのvで、Haveは「持っている」ブロックを表しているのかなと思います。
while ループ
ループの中を見ていきます。
ループの終了条件は、pindex に値があるかどうか、または pindex がジェネシスブロックに到達した場合です。
まず、pindex が指すブロックのハッシュ値を早速 vHave に追加していますね。
code:cpp
vHave.push_back(pindex->GetBlockHash());
pindex のブロック高を調べて、0つまりジェネシスブロックの場合は、これで処理を終了します。
code:cpp
// Stop when we have added the genesis block.
if (pindex->nHeight == 0)
break;
ジェネシスブロックでない場合は以下の処理が使われます。
code:cpp
// Exponentially larger steps back, plus the genesis block.
int nHeight = std::max(pindex->nHeight - nStep, 0);
if (Contains(pindex)) {
// Use O(1) CChain index if possible.
} else {
// Otherwise, use O(log n) skiplist.
pindex = pindex->GetAncestor(nHeight);
}
nStep は最初は1で初期化されています。
code:cpp
int nHeight = std::max(pindex->nHeight - nStep, 0);
nHeight に、 nStep 分だけ前のブロックのブロック高を入れています。0とのmaxをとっているのは、マイナスになることを避けるためでしょう。
code:cpp
if (Contains(pindex)) {
// Use O(1) CChain index if possible.
}
CChain::Contails() は、チェーンに引数のインデックスが指すブロックが含まれるかを判定する関数です。
含まれる場合は、そのブロック高のブロックを pindex にセットして処理を続けます。
次のループで、この pindex が指すブロックを vHave に加えることになるので、pindex がチェーンに含まれるのであれば、その祖先となるブロックも含まれるだろうということでしょうか。
code: cpp
} else {
// Otherwise, use O(log n) skiplist.
pindex = pindex->GetAncestor(nHeight);
}
含まれない場合は、CBlockIndex::GetAncestor() メソッドを使って、nHeight に対応するブロック高のブロックを取得しています。pindex がチェーン外なので、その祖先もチェーン外のブロックである可能性があるためかと思います。
code:cpp
if (vHave.size() > 10)
nStep *= 2;
vHave の数が10より大きい場合、 nStep を2倍しています。
nStepは1から始まるので、最初の10個は直接つながっているブロックを取得して、それ以降は、2個前、4個前、8個前、、、と間隔を倍にしながらgenesisブロックに至るまで続けるようです。
getheaders メッセージを受け取ったときのコードでみましたが、vHaveの数が101を超えると、不正とみなして、peerとの接続を切ります。現在のtip のブロック高が 594609 なので、計算すると vHave のサイズは 31 になり、間隔は指数的に広がりますので、101 という数字は十分大きな数に見えます。