代码源码和逐行解读
今天来超详细的逐行解读比特币代码中的 bitcoin/src/init.cpp 文件中的函数 StartIndexBackgroundSync,下面是代码源码。
bool StartIndexBackgroundSync(NodeContext& node)
{
// Find the oldest block among all indexes.
// This block is used to verify that we have the required blocks' data stored on disk,
// starting from that point up to the current tip.
// indexes_start_block='nullptr' means "start from height 0".
std::optional<const CBlockIndex*> indexes_start_block;
std::string older_index_name;
ChainstateManager& chainman = *Assert(node.chainman);
const Chainstate& chainstate = WITH_LOCK(::cs_main, return chainman.GetChainstateForIndexing());
const CChain& index_chain = chainstate.m_chain;
for (auto index : node.indexes) {
const IndexSummary& summary = index->GetSummary();
if (summary.synced) continue;
// Get the last common block between the index best block and the active chain
LOCK(::cs_main);
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(summary.best_block_hash);
if (!index_chain.Contains(pindex)) {
pindex = index_chain.FindFork(pindex);
}
if (!indexes_start_block || !pindex || pindex->nHeight < indexes_start_block.value()->nHeight) {
indexes_start_block = pindex;
older_index_name = summary.name;
if (!pindex) break; // Starting from genesis so no need to look for earlier block.
}
};
// Verify all blocks needed to sync to current tip are present.
if (indexes_start_block) {
LOCK(::cs_main);
const CBlockIndex* start_block = *indexes_start_block;
if (!start_block) start_block = chainman.ActiveChain().Genesis();
if (!chainman.m_blockman.CheckBlockDataAvailability(*index_chain.Tip(), *Assert(start_block))) {
return InitError(strprintf(Untranslated("%s best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)"), older_index_name));
}
}
// Start threads
for (auto index : node.indexes) if (!index->StartBackgroundSync()) return false;
return true;
}
后面是对本段代码的逐行解读说明
这段代码是一个函数 StartIndexBackgroundSync
,它的作用是在比特币节点中启动后台的索引同步。我将逐行解释代码的作用:
bool StartIndexBackgroundSync(NodeContext& node)
{
定义了一个函数 StartIndexBackgroundSync
,它接受一个 NodeContext
类型的参数 node
。这个 node
对象包含节点的上下文信息,如索引、链状态管理器等。
// Find the oldest block among all indexes.
// This block is used to verify that we have the required blocks' data stored on disk,
// starting from that point up to the current tip.
// indexes_start_block='nullptr' means "start from height 0".
std::optional<const CBlockIndex*> indexes_start_block;
std::string older_index_name;
声明了一个 std::optional
类型的变量 indexes_start_block
,这表示它可能包含一个指向 CBlockIndex
的指针,或者没有值(意味着从创世块开始)。older_index_name
用于存储最旧的索引的名称。
ChainstateManager& chainman = *Assert(node.chainman);
通过断言获取节点的链状态管理器 chainman
的引用。Assert
是一个宏或函数,用于检查 node.chainman
是否非空,并在为空时提供有用的调试信息。
const Chainstate& chainstate = WITH_LOCK(::cs_main, return chainman.GetChainstateForIndexing());
const CChain& index_chain = chainstate.m_chain;
在持有主要的互斥锁 cs_main
的情况下,获取用于索引的链状态,然后获取这个链状态的区块链引用 index_chain
。
for (auto index : node.indexes) {
const IndexSummary& summary = index->GetSummary();
if (summary.synced) continue;
遍历节点中的所有索引,并获取每个索引的摘要。如果索引已经同步,则跳过此索引。
LOCK(::cs_main);
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(summary.best_block_hash);
if (!index_chain.Contains(pindex)) {
pindex = index_chain.FindFork(pindex);
}
在持有 cs_main
锁的情况下,通过索引的最佳块哈希查找区块索引。如果索引链不包含该区块,那么找到该区块在索引链上的分叉点。
if (!indexes_start_block || !pindex || pindex->nHeight < indexes_start_block.value()->nHeight) {
indexes_start_block = pindex;
older_index_name = summary.name;
if (!pindex) break; // Starting from genesis so no need to look for earlier block.
}
};
如果当前索引的起始区块还没有被设置,或者找到的区块比现有的起始区块更旧(即区块高度更低),则更新起始区块为当前区块,并记录该索引的名称。如果没有找到区块(pindex
为空),则意味着应该从创世块开始同步,因此跳出循环。
// Verify all blocks needed to sync to current tip are present.
if (indexes_start_block) {
LOCK(::cs_main);
const CBlockIndex* start_block = *indexes_start_block;
if (!start_block) start_block = chainman.ActiveChain().Genesis();
if (!chainman.m_blockman.CheckBlockDataAvailability(*index_chain.Tip(), *Assert(start_block))) {
return InitError(strprintf(Untranslated("%s best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)"), older_index_name));
}
}
如果有起始区块,检查从该区块到当前链尖所需的所有区块数据是否都存在。如果数据不完整(可能是因为数据被修剪),则返回初始化错误信息,提示用户禁用索引或重新索引。
// Start threads
for (auto index : node.indexes) if (!index->StartBackgroundSync()) return false;
return true;
}
遍历所有索引,启动它们的后台同步线程。如果任何索引启动失败,则函数返回 false
。如果所有索引都成功启动,函数返回 true
。
这个函数的整体目的是为了启动节点中所有索引的后台同步,确保所有的区块数据都可用,并且在开始同步之前,确定同步的起点。这是确保节点索引数据完整性和一致性的重要步骤。
下面让我们从功能和流程上详细剖析这段代码:
功能
StartIndexBackgroundSync
函数的主要目的是初始化并启动比特币节点中所有未完成同步的索引的后台同步过程。这个功能对于确保节点上的区块索引数据是最新和完整的至关重要。索引可以是事务索引、地址索引等,它们允许节点快速检索特定的区块链信息。
流程
-
初始化变量:
indexes_start_block
:用来确定所有索引中最旧的区块,即同步的起点。older_index_name
:存储最旧索引的名称,用于错误报告。chainman
:引用节点的链状态管理器,管理区块链的不同“链状态”。chainstate
:通过锁定主线程锁cs_main
来获取索引所需的链状态。index_chain
:从chainstate
获取当前活跃的区块链。
-
确定同步起点:
- 遍历所有的索引。
- 对于每个未同步的索引:
- 查找索引指定的最佳块在区块链中的位置。
- 如果最佳块不在当前活跃的链上,找到它与当前链的最后一个共同区块(分叉点)。
- 如果这个分叉点比之前记录的
indexes_start_block
更旧,或者indexes_start_block
未设置,那么更新indexes_start_block
为这个分叉点,并记录索引名称。
-
验证区块数据可用性:
- 如果确定了同步起点:
- 确保从同步起点到当前链尖的所有区块数据都在本地可用。
- 如果数据不可用(可能因为修剪),报告错误并提示用户禁用索引或重新索引。
- 如果确定了同步起点:
-
启动后台同步线程:
- 遍历所有索引。
- 调用每个索引的
StartBackgroundSync
方法来启动它们的后台同步线程。 - 如果任何索引的同步启动失败,函数返回
false
。 - 如果所有索引都成功启动同步,函数返回
true
。
在整个流程中,代码使用了多个锁(LOCK(::cs_main)
)来保证在访问共享数据结构时的线程安全。这是必要的,因为比特币客户端通常是多线程的,且多个线程可能会同时操作区块链数据结构。
此外,函数在检查区块数据可用性时,使用了 CheckBlockDataAvailability
方法。这是重要的,因为如果节点配置为修剪模式,那么旧的区块数据可能已经从磁盘上删除,这会导致索引无法完成同步。
最后,函数的返回值告知调用者索引同步是否成功启动。这个返回值可以用来决定是否继续节点的启动流程,或者在出现问题时停止并报告错误。