Bitcoin Core 0.17.0
Genesis Blockの生成
Genesis Block は通常のブロックとは違い、データファイルにストアされているわけではなく、ノード立ち上げ時に、ハードコードされたパラメータから生成されているようです。パラメータは src/chainparams.cpp で見ることができます。有名なGenesis ブロックのコインベーストランザクションの文字列もコード中に見つけることができます。
code:src/chainparams.cpp
static CBlock CreateGenesisBlock(uint32_t nTime, uint32_t nNonce, uint32_t nBits, int32_t nVersion, const CAmount& genesisReward)
{
const char* pszTimestamp = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks";
const CScript genesisOutputScript = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
return CreateGenesisBlock(pszTimestamp, genesisOutputScript, nTime, nNonce, nBits, nVersion, genesisReward);
}
このメソッドはは以下から呼ばれます。
code:src/chainparams.cpp
genesis = CreateGenesisBlock(1231006505, 2083236893, 0x1d00ffff, 1, 50 * COIN);
ブロックの生成時間やノンスなどがハードコーディングされていますね。タイムスタンプをHumanizeすると 2009-01-03T18:15:05+00:00 となり、エクスプローラーで確認できる Genesis Block の生成時間 と一致することがわかります。 さらに、下のメソッドがよばれ、実際に CBlock型のオブジェクトが生成されていることがわかります。
code:src/chainparams.cpp
static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesisOutputScript, uint32_t nTime, uint32_t nNonce, uint32_t nBits, int32_t nVersion, const CAmount& genesisReward)
{
CMutableTransaction txNew;
txNew.nVersion = 1;
txNew.vin.resize(1);
txNew.vout.resize(1);
txNew.vin0.scriptSig = CScript() << 486604799 << CScriptNum(4) << std::vector<unsigned char>((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); txNew.vout0.nValue = genesisReward; txNew.vout0.scriptPubKey = genesisOutputScript; CBlock genesis;
genesis.nTime = nTime;
genesis.nBits = nBits;
genesis.nNonce = nNonce;
genesis.nVersion = nVersion;
genesis.vtx.push_back(MakeTransactionRef(std::move(txNew)));
genesis.hashPrevBlock.SetNull();
genesis.hashMerkleRoot = BlockMerkleRoot(genesis);
return genesis;
}
Genesis Block なので、前のブロックのハッシュ値を記録する領域 hashPrevBlock は Null に設定さていますね。
生成された GenesisBlock はそれに続く処理の中で、検証されています。
code:src/chainparams.cpp
genesis = CreateGenesisBlock(1231006505, 2083236893, 0x1d00ffff, 1, 50 * COIN);
consensus.hashGenesisBlock = genesis.GetHash();
assert(consensus.hashGenesisBlock == uint256S("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"));
assert(genesis.hashMerkleRoot == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"));
所感
Genesis Blockも他のブロックと同じ様にP2Pネットワークで伝播され受け取るのかなと思っていたので、ちょっと意外でした。でも、GenesisBlockの内容が代わると、もはやそれは別のチェーンとなるということを考えると、GenesisBlockまで含めてチェーンのパラメータと捉える考え方は納得感がありますね。そういう理由で src/chainparams.cpp に定義されているんだと思います。
ブロックの検証
正しいブロックを作るには、ブロックがどの様に検証されるかを理解することが必要ですね。ということで、ブロックの検証処理を調べてみます。ブロックの検証と言いましたが、処理としてはブロックヘッダーの検証と、ブロック自体の検証があります。フルノードが他のノードからブロックのデータを受け取るとき、まずヘッダーのデータを受け取ってから、
ブロックヘッダーの検証
ブロックヘッダーの検証はここが入り口になりそうですね。
code:src/validation.h
/**
* Process incoming block headers.
*
* May not be called in a
* validationinterface callback.
*
* @paramin block The block headers themselves * @paramout state This may be set to an Error state if any error occurred processing them * @paramin chainparams The params for the chain we want to connect to * @paramout ppindex If set, the pointer will be set to point to the last new block index object for the given headers * @paramout first_invalid First header that fails validation, if one exists */
bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr, CBlockHeader* first_invalid = nullptr) LOCKS_EXCLUDED(cs_main);
この処理は P2P の通信プロトコルで、HEADERSメッセージを受け取った時に呼ばれるようです。
たどっていくと、検証処理の実体が見つかります。
code:src/validation.cpp
bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex)
{
AssertLockHeld(cs_main);
// Check for duplicate
uint256 hash = block.GetHash();
BlockMap::iterator miSelf = mapBlockIndex.find(hash);
CBlockIndex *pindex = nullptr;
if (hash != chainparams.GetConsensus().hashGenesisBlock) {
if (miSelf != mapBlockIndex.end()) {
// Block header is already known.
pindex = miSelf->second;
if (ppindex)
*ppindex = pindex;
if (pindex->nStatus & BLOCK_FAILED_MASK)
return state.Invalid(error("%s: block %s is marked invalid", __func__, hash.ToString()), 0, "duplicate");
return true;
}
if (!CheckBlockHeader(block, state, chainparams.GetConsensus()))
return error("%s: Consensus::CheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state));
// Get prev block index
CBlockIndex* pindexPrev = nullptr;
BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock);
if (mi == mapBlockIndex.end())
return state.DoS(10, error("%s: prev block not found", __func__), 0, "prev-blk-not-found");
pindexPrev = (*mi).second;
if (pindexPrev->nStatus & BLOCK_FAILED_MASK)
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime()))
return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state));
// If the previous block index isn't valid, determine if it descends from any block which
// has been found invalid (m_failed_blocks), then mark pindexPrev and any blocks
// between them as failed.
if (!pindexPrev->IsValid(BLOCK_VALID_SCRIPTS)) {
for (const CBlockIndex* failedit : m_failed_blocks) {
if (pindexPrev->GetAncestor(failedit->nHeight) == failedit) {
assert(failedit->nStatus & BLOCK_FAILED_VALID);
CBlockIndex* invalid_walk = pindexPrev;
while (invalid_walk != failedit) {
invalid_walk->nStatus |= BLOCK_FAILED_CHILD;
setDirtyBlockIndex.insert(invalid_walk);
invalid_walk = invalid_walk->pprev;
}
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
}
}
}
}
if (pindex == nullptr)
pindex = AddToBlockIndex(block);
if (ppindex)
*ppindex = pindex;
CheckBlockIndex(chainparams.GetConsensus());
return true;
}
CChainSate クラス
見ていく前に CChainState クラスについて確認します。CChainState クラスは src/validation.cpp で定義されているクラスであり、グローバル変数 g_chainstate に唯一のインスタンスが格納されるシングルトンであるようです。
上の関数で参照されている mapBlockIndex はこの g_chainstate の公開メンバ変数です。
以下はコメントから引用
CChainStateは、現在のベストチェーンとヘッダーツリーのローカル知識を更新するためのAPIを提供し、提供します。
一般に、現在のブロックツリーへのアクセスと、新しいデータを提供する機能を提供します。新しいデータは、適切に検証され、必要に応じてその状態に組み込まれます。
結局のところ、ここでのAPIは、消費可能なlibconsensusライブラリとして外部に公開されることを目標としているため、追加された関数は他のクラスメンバ関数、コンセンサスライブラリの他の部分の純関数、検証インタフェースを介したコールバック、 (最終的にはこれもコールバック経由で行われます)。
つまり現在のベストチェーンへの参照や、その他現在のチェーンに関する様々な情報へアクセスするための入り口として機能するようです。
g_chainstate::mapBlockIndex
mapBlockIndex は BlockMap 型のインスタンスで、ブロックハッシュから各ブロックの実体へアクセスするためのマップを提供しているようです。
BlockMap は以下のように unordered_map の別名です。
code:cpp
typedef std::unordered_map<uint256, CBlockIndex*, BlockHasher> BlockMap;
やや長いですね。順番に見ていきます。
code:cpp
AssertLockHeld(cs_main);
これは非同期処理のための記述ぽいですね。 src/sync.h に実装があります。また別の機会に掘り下げたいです。
code:cpp
if (hash != chainparams.GetConsensus().hashGenesisBlock) {
検証対象のブロックがGenesisブロック出ない場合、if文のブロックの中が実行されます。
ブロックの検証
code:src/validation.h
/**
* Process an incoming block. This only returns after the best known valid
* block is made active. Note that it does not, however, guarantee that the
* specific block passed to it has been checked for validity!
*
* If you want to *possibly* get feedback on whether pblock is valid, you must
* install a CValidationInterface (see validationinterface.h) - this will have
* its BlockChecked method called whenever *any* block completes validation.
*
* Note that we guarantee that either the proof-of-work is valid on pblock, or
* (and possibly also) BlockChecked will have been called.
*
* May not be called in a
* validationinterface callback.
*
* @paramin pblock The block we want to process. * @paramin fForceProcessing Process this block even if unrequested; used for non-network block sources and whitelisted peers. * @paramout fNewBlock A boolean which is set to indicate if the block was first received via this call * @return True if state.IsValid()
*/
bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool* fNewBlock) LOCKS_EXCLUDED(cs_main);
ここが怪しいですね。実装を見てみましょう。
code:src/validation.cpp
bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool *fNewBlock)
{
AssertLockNotHeld(cs_main);
{
CBlockIndex *pindex = nullptr;
if (fNewBlock) *fNewBlock = false;
CValidationState state;
// Ensure that CheckBlock() passes before calling AcceptBlock, as
// belt-and-suspenders.
bool ret = CheckBlock(*pblock, state, chainparams.GetConsensus());
LOCK(cs_main);
if (ret) {
// Store to disk
ret = g_chainstate.AcceptBlock(pblock, state, chainparams, &pindex, fForceProcessing, nullptr, fNewBlock);
}
if (!ret) {
GetMainSignals().BlockChecked(*pblock, state);
return error("%s: AcceptBlock FAILED (%s)", __func__, FormatStateMessage(state));
}
}
NotifyHeaderTip();
CValidationState state; // Only used to report errors, not invalidity - ignore it
if (!g_chainstate.ActivateBestChain(state, chainparams, pblock))
return error("%s: ActivateBestChain failed (%s)", __func__, FormatStateMessage(state));
return true;
}
ブロックの生成
TIPS
ブロックを16進数のバイト列にする。
code: cpp
size_t bsize = sizeof(block);
unsigned char* data = new unsigned charbsize; memcpy(data, &block, bsize);
std::cout << "block: " << HexStr(data, data + bsize) << std::endl;