今天来详细解读比特币源代码:bitcoin/src/init.cpp 中的一个非常重要的函数:AppInitMain,这个函数是用来初始化bitcoin 节点服务程序(客户端同时也是服务器)的主线程。
下面是代码源码
bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
{
const ArgsManager& args = *Assert(node.args);
const CChainParams& chainparams = Params();
auto opt_max_upload = ParseByteUnits(args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET), ByteUnit::M);
if (!opt_max_upload) {
return InitError(strprintf(_("Unable to parse -maxuploadtarget: '%s'"), args.GetArg("-maxuploadtarget", "")));
}
// ********************************************************* Step 4a: application initialization
if (!CreatePidFile(args)) {
// Detailed error printed inside CreatePidFile().
return false;
}
if (!init::StartLogging(args)) {
// Detailed error printed inside StartLogging().
return false;
}
LogPrintf("Using at most %i automatic connections (%i file descriptors available)\n", nMaxConnections, nFD);
// Warn about relative -datadir path.
if (args.IsArgSet("-datadir") && !args.GetPathArg("-datadir").is_absolute()) {
LogPrintf("Warning: relative datadir option '%s' specified, which will be interpreted relative to the "
"current working directory '%s'. This is fragile, because if bitcoin is started in the future "
"from a different location, it will be unable to locate the current data files. There could "
"also be data loss if bitcoin is started while in a temporary directory.\n",
args.GetArg("-datadir", ""), fs::PathToString(fs::current_path()));
}
ValidationCacheSizes validation_cache_sizes{};
ApplyArgsManOptions(args, validation_cache_sizes);
if (!InitSignatureCache(validation_cache_sizes.signature_cache_bytes)
|| !InitScriptExecutionCache(validation_cache_sizes.script_execution_cache_bytes))
{
return InitError(strprintf(_("Unable to allocate memory for -maxsigcachesize: '%s' MiB"), args.GetIntArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_BYTES >> 20)));
}
assert(!node.scheduler);
node.scheduler = std::make_unique<CScheduler>();
// Start the lightweight task scheduler thread
node.scheduler->m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { node.scheduler->serviceQueue(); });
// Gather some entropy once per minute.
node.scheduler->scheduleEvery([]{
RandAddPeriodic();
}, std::chrono::minutes{1});
// Check disk space every 5 minutes to avoid db corruption.
node.scheduler->scheduleEvery([&args, &node]{
constexpr uint64_t min_disk_space = 50 << 20; // 50 MB
if (!CheckDiskSpace(args.GetBlocksDirPath(), min_disk_space)) {
LogPrintf("Shutting down due to lack of disk space!\n");
if (!(*Assert(node.shutdown))()) {
LogPrintf("Error: failed to send shutdown signal after disk space check\n");
}
}
}, std::chrono::minutes{5});
GetMainSignals().RegisterBackgroundSignalScheduler(*node.scheduler);
// Create client interfaces for wallets that are supposed to be loaded
// according to -wallet and -disablewallet options. This only constructs
// the interfaces, it doesn't load wallet data. Wallets actually get loaded
// when load() and start() interface methods are called below.
g_wallet_init_interface.Construct(node);
uiInterface.InitWallet();
/* Register RPC commands regardless of -server setting so they will be
* available in the GUI RPC console even if external calls are disabled.
*/
RegisterAllCoreRPCCommands(tableRPC);
for (const auto& client : node.chain_clients) {
client->registerRpcs();
}
#if ENABLE_ZMQ
RegisterZMQRPCCommands(tableRPC);
#endif
/* Start the RPC server already. It will be started in "warmup" mode
* and not really process calls already (but it will signify connections
* that the server is there and will be ready later). Warmup mode will
* be disabled when initialisation is finished.
*/
if (args.GetBoolArg("-server", false)) {
uiInterface.InitMessage_connect(SetRPCWarmupStatus);
if (!AppInitServers(node))
return InitError(_("Unable to start HTTP server. See debug log for details."));
}
// ********************************************************* Step 5: verify wallet database integrity
for (const auto& client : node.chain_clients) {
if (!client->verify()) {
return false;
}
}
// ********************************************************* Step 6: network initialization
// Note that we absolutely cannot open any actual connections
// until the very end ("start node") as the UTXO/block state
// is not yet setup and may end up being set up twice if we
// need to reindex later.
fListen = args.GetBoolArg("-listen", DEFAULT_LISTEN);
fDiscover = args.GetBoolArg("-discover", true);
PeerManager::Options peerman_opts{};
ApplyArgsManOptions(args, peerman_opts);
{
// Read asmap file if configured
std::vector<bool> asmap;
if (args.IsArgSet("-asmap")) {
fs::path asmap_path = args.GetPathArg("-asmap", DEFAULT_ASMAP_FILENAME);
if (!asmap_path.is_absolute()) {
asmap_path = args.GetDataDirNet() / asmap_path;
}
if (!fs::exists(asmap_path)) {
InitError(strprintf(_("Could not find asmap file %s"), fs::quoted(fs::PathToString(asmap_path))));
return false;
}
asmap = DecodeAsmap(asmap_path);
if (asmap.size() == 0) {
InitError(strprintf(_("Could not parse asmap file %s"), fs::quoted(fs::PathToString(asmap_path))));
return false;
}
const uint256 asmap_version = (HashWriter{} << asmap).GetHash();
LogPrintf("Using asmap version %s for IP bucketing\n", asmap_version.ToString());
} else {
LogPrintf("Using /16 prefix for IP bucketing\n");
}
// Initialize netgroup manager
assert(!node.netgroupman);
node.netgroupman = std::make_unique<NetGroupManager>(std::move(asmap));
// Initialize addrman
assert(!node.addrman);
uiInterface.InitMessage(_("Loading P2P addresses…").translated);
auto addrman{LoadAddrman(*node.netgroupman, args)};
if (!addrman) return InitError(util::ErrorString(addrman));
node.addrman = std::move(*addrman);
}
assert(!node.banman);
node.banman = std::make_unique<BanMan>(args.GetDataDirNet() / "banlist", &uiInterface, args.GetIntArg("-bantime", DEFAULT_MISBEHAVING_BANTIME));
assert(!node.connman);
node.connman = std::make_unique<CConnman>(GetRand<uint64_t>(),
GetRand<uint64_t>(),
*node.addrman, *node.netgroupman, chainparams, args.GetBoolArg("-networkactive", true));
assert(!node.fee_estimator);
// Don't initialize fee estimation with old data if we don't relay transactions,
// as they would never get updated.
if (!peerman_opts.ignore_incoming_txs) {
bool read_stale_estimates = args.GetBoolArg("-acceptstalefeeestimates", DEFAULT_ACCEPT_STALE_FEE_ESTIMATES);
if (read_stale_estimates && (chainparams.GetChainType() != ChainType::REGTEST)) {
return InitError(strprintf(_("acceptstalefeeestimates is not supported on %s chain."), chainparams.GetChainTypeString()));
}
node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(args), read_stale_estimates);
// Flush estimates to disk periodically
CBlockPolicyEstimator* fee_estimator = node.fee_estimator.get();
node.scheduler->scheduleEvery([fee_estimator] { fee_estimator->FlushFeeEstimates(); }, FEE_FLUSH_INTERVAL);
RegisterValidationInterface(fee_estimator);
}
// Check port numbers
for (const std::string port_option : {
"-port",
"-rpcport",
}) {
if (args.IsArgSet(port_option)) {
const std::string port = args.GetArg(port_option, "");
uint16_t n;
if (!ParseUInt16(port, &n) || n == 0) {
return InitError(InvalidPortErrMsg(port_option, port));
}
}
}
for (const std::string port_option : {
"-i2psam",
"-onion",
"-proxy",
"-rpcbind",
"-torcontrol",
"-whitebind",
"-zmqpubhashblock",
"-zmqpubhashtx",
"-zmqpubrawblock",
"-zmqpubrawtx",
"-zmqpubsequence",
}) {
for (const std::string& socket_addr : args.GetArgs(port_option)) {
std::string host_out;
uint16_t port_out{0};
if (!SplitHostPort(socket_addr, port_out, host_out)) {
return InitError(InvalidPortErrMsg(port_option, socket_addr));
}
}
}
for (const std::string& socket_addr : args.GetArgs("-bind")) {
std::string host_out;
uint16_t port_out{0};
std::string bind_socket_addr = socket_addr.substr(0, socket_addr.rfind('='));
if (!SplitHostPort(bind_socket_addr, port_out, host_out)) {
return InitError(InvalidPortErrMsg("-bind", socket_addr));
}
}
// sanitize comments per BIP-0014, format user agent and check total size
std::vector<std::string> uacomments;
for (const std::string& cmt : args.GetArgs("-uacomment")) {
if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT))
return InitError(strprintf(_("User Agent comment (%s) contains unsafe characters."), cmt));
uacomments.push_back(cmt);
}
strSubVersion = FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments);
if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) {
return InitError(strprintf(_("Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments."),
strSubVersion.size(), MAX_SUBVERSION_LENGTH));
}
if (args.IsArgSet("-onlynet")) {
g_reachable_nets.RemoveAll();
for (const std::string& snet : args.GetArgs("-onlynet")) {
enum Network net = ParseNetwork(snet);
if (net == NET_UNROUTABLE)
return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet));
g_reachable_nets.Add(net);
}
}
if (!args.IsArgSet("-cjdnsreachable")) {
if (args.IsArgSet("-onlynet") && g_reachable_nets.Contains(NET_CJDNS)) {
return InitError(
_("Outbound connections restricted to CJDNS (-onlynet=cjdns) but "
"-cjdnsreachable is not provided"));
}
g_reachable_nets.Remove(NET_CJDNS);
}
// Now g_reachable_nets.Contains(NET_CJDNS) is true if:
// 1. -cjdnsreachable is given and
// 2.1. -onlynet is not given or
// 2.2. -onlynet=cjdns is given
// Requesting DNS seeds entails connecting to IPv4/IPv6, which -onlynet options may prohibit:
// If -dnsseed=1 is explicitly specified, abort. If it's left unspecified by the user, we skip
// the DNS seeds by adjusting -dnsseed in InitParameterInteraction.
if (args.GetBoolArg("-dnsseed") == true && !g_reachable_nets.Contains(NET_IPV4) && !g_reachable_nets.Contains(NET_IPV6)) {
return InitError(strprintf(_("Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6")));
};
// Check for host lookup allowed before parsing any network related parameters
fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
Proxy onion_proxy;
bool proxyRandomize = args.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);
// -proxy sets a proxy for all outgoing network traffic
// -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default
std::string proxyArg = args.GetArg("-proxy", "");
if (proxyArg != "" && proxyArg != "0") {
const std::optional<CService> proxyAddr{Lookup(proxyArg, 9050, fNameLookup)};
if (!proxyAddr.has_value()) {
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
}
Proxy addrProxy = Proxy(proxyAddr.value(), proxyRandomize);
if (!addrProxy.IsValid())
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
SetProxy(NET_IPV4, addrProxy);
SetProxy(NET_IPV6, addrProxy);
SetProxy(NET_CJDNS, addrProxy);
SetNameProxy(addrProxy);
onion_proxy = addrProxy;
}
const bool onlynet_used_with_onion{args.IsArgSet("-onlynet") && g_reachable_nets.Contains(NET_ONION)};
// -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses
// -noonion (or -onion=0) disables connecting to .onion entirely
// An empty string is used to not override the onion proxy (in which case it defaults to -proxy set above, or none)
std::string onionArg = args.GetArg("-onion", "");
if (onionArg != "") {
if (onionArg == "0") { // Handle -noonion/-onion=0
onion_proxy = Proxy{};
if (onlynet_used_with_onion) {
return InitError(
_("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for "
"reaching the Tor network is explicitly forbidden: -onion=0"));
}
} else {
const std::optional<CService> addr{Lookup(onionArg, 9050, fNameLookup)};
if (!addr.has_value() || !addr->IsValid()) {
return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
}
onion_proxy = Proxy{addr.value(), proxyRandomize};
}
}
if (onion_proxy.IsValid()) {
SetProxy(NET_ONION, onion_proxy);
} else {
// If -listenonion is set, then we will (try to) connect to the Tor control port
// later from the torcontrol thread and may retrieve the onion proxy from there.
const bool listenonion_disabled{!args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)};
if (onlynet_used_with_onion && listenonion_disabled) {
return InitError(
_("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for "
"reaching the Tor network is not provided: none of -proxy, -onion or "
"-listenonion is given"));
}
g_reachable_nets.Remove(NET_ONION);
}
for (const std::string& strAddr : args.GetArgs("-externalip")) {
const std::optional<CService> addrLocal{Lookup(strAddr, GetListenPort(), fNameLookup)};
if (addrLocal.has_value() && addrLocal->IsValid())
AddLocal(addrLocal.value(), LOCAL_MANUAL);
else
return InitError(ResolveErrMsg("externalip", strAddr));
}
#if ENABLE_ZMQ
g_zmq_notification_interface = CZMQNotificationInterface::Create(
[&chainman = node.chainman](CBlock& block, const CBlockIndex& index) {
assert(chainman);
return chainman->m_blockman.ReadBlockFromDisk(block, index);
});
if (g_zmq_notification_interface) {
RegisterValidationInterface(g_zmq_notification_interface.get());
}
#endif
// ********************************************************* Step 7: load block chain
node.notifications = std::make_unique<KernelNotifications>(*Assert(node.shutdown), node.exit_status);
ReadNotificationArgs(args, *node.notifications);
fReindex = args.GetBoolArg("-reindex", false);
bool fReindexChainState = args.GetBoolArg("-reindex-chainstate", false);
ChainstateManager::Options chainman_opts{
.chainparams = chainparams,
.datadir = args.GetDataDirNet(),
.adjusted_time_callback = GetAdjustedTime,
.notifications = *node.notifications,
};
Assert(ApplyArgsManOptions(args, chainman_opts)); // no error can happen, already checked in AppInitParameterInteraction
BlockManager::Options blockman_opts{
.chainparams = chainman_opts.chainparams,
.blocks_dir = args.GetBlocksDirPath(),
.notifications = chainman_opts.notifications,
};
Assert(ApplyArgsManOptions(args, blockman_opts)); // no error can happen, already checked in AppInitParameterInteraction
// cache size calculations
CacheSizes cache_sizes = CalculateCacheSizes(args, g_enabled_filter_types.size());
LogPrintf("Cache configuration:\n");
LogPrintf("* Using %.1f MiB for block index database\n", cache_sizes.block_tree_db * (1.0 / 1024 / 1024));
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
LogPrintf("* Using %.1f MiB for transaction index database\n", cache_sizes.tx_index * (1.0 / 1024 / 1024));
}
for (BlockFilterType filter_type : g_enabled_filter_types) {
LogPrintf("* Using %.1f MiB for %s block filter index database\n",
cache_sizes.filter_index * (1.0 / 1024 / 1024), BlockFilterTypeName(filter_type));
}
LogPrintf("* Using %.1f MiB for chain state database\n", cache_sizes.coins_db * (1.0 / 1024 / 1024));
assert(!node.mempool);
assert(!node.chainman);
CTxMemPool::Options mempool_opts{
.check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0,
};
auto result{ApplyArgsManOptions(args, chainparams, mempool_opts)};
if (!result) {
return InitError(util::ErrorString(result));
}
mempool_opts.check_ratio = std::clamp<int>(mempool_opts.check_ratio, 0, 1'000'000);
int64_t descendant_limit_bytes = mempool_opts.limits.descendant_size_vbytes * 40;
if (mempool_opts.max_size_bytes < 0 || mempool_opts.max_size_bytes < descendant_limit_bytes) {
return InitError(strprintf(_("-maxmempool must be at least %d MB"), std::ceil(descendant_limit_bytes / 1'000'000.0)));
}
LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), mempool_opts.max_size_bytes * (1.0 / 1024 / 1024));
for (bool fLoaded = false; !fLoaded && !ShutdownRequested(node);) {
node.mempool = std::make_unique<CTxMemPool>(mempool_opts);
node.chainman = std::make_unique<ChainstateManager>(*Assert(node.shutdown), chainman_opts, blockman_opts);
ChainstateManager& chainman = *node.chainman;
// This is defined and set here instead of inline in validation.h to avoid a hard
// dependency between validation and index/base, since the latter is not in
// libbitcoinkernel.
chainman.restart_indexes = [&node]() {
LogPrintf("[snapshot] restarting indexes\n");
// Drain the validation interface queue to ensure that the old indexes
// don't have any pending work.
SyncWithValidationInterfaceQueue();
for (auto* index : node.indexes) {
index->Interrupt();
index->Stop();
if (!(index->Init() && index->StartBackgroundSync())) {
LogPrintf("[snapshot] WARNING failed to restart index %s on snapshot chain\n", index->GetName());
}
}
};
node::ChainstateLoadOptions options;
options.mempool = Assert(node.mempool.get());
options.reindex = node::fReindex;
options.reindex_chainstate = fReindexChainState;
options.prune = chainman.m_blockman.IsPruneMode();
options.check_blocks = args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS);
options.check_level = args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL);
options.require_full_verification = args.IsArgSet("-checkblocks") || args.IsArgSet("-checklevel");
options.coins_error_cb = [] {
uiInterface.ThreadSafeMessageBox(
_("Error reading from database, shutting down."),
"", CClientUIInterface::MSG_ERROR);
};
uiInterface.InitMessage(_("Loading block index…").translated);
const auto load_block_index_start_time{SteadyClock::now()};
auto catch_exceptions = [](auto&& f) {
try {
return f();
} catch (const std::exception& e) {
LogPrintf("%s\n", e.what());
return std::make_tuple(node::ChainstateLoadStatus::FAILURE, _("Error opening block database"));
}
};
auto [status, error] = catch_exceptions([&]{ return LoadChainstate(chainman, cache_sizes, options); });
if (status == node::ChainstateLoadStatus::SUCCESS) {
uiInterface.InitMessage(_("Verifying blocks…").translated);
if (chainman.m_blockman.m_have_pruned && options.check_blocks > MIN_BLOCKS_TO_KEEP) {
LogWarning("pruned datadir may not have more than %d blocks; only checking available blocks\n",
MIN_BLOCKS_TO_KEEP);
}
std::tie(status, error) = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options);});
if (status == node::ChainstateLoadStatus::SUCCESS) {
fLoaded = true;
LogPrintf(" block index %15dms\n", Ticks<std::chrono::milliseconds>(SteadyClock::now() - load_block_index_start_time));
}
}
if (status == node::ChainstateLoadStatus::FAILURE_FATAL || status == node::ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB || status == node::ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE) {
return InitError(error);
}
if (!fLoaded && !ShutdownRequested(node)) {
// first suggest a reindex
if (!options.reindex) {
bool fRet = uiInterface.ThreadSafeQuestion(
error + Untranslated(".\n\n") + _("Do you want to rebuild the block database now?"),
error.original + ".\nPlease restart with -reindex or -reindex-chainstate to recover.",
"", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT);
if (fRet) {
fReindex = true;
if (!Assert(node.shutdown)->reset()) {
LogPrintf("Internal error: failed to reset shutdown signal.\n");
}
} else {
LogPrintf("Aborted block database rebuild. Exiting.\n");
return false;
}
} else {
return InitError(error);
}
}
}
// As LoadBlockIndex can take several minutes, it's possible the user
// requested to kill the GUI during the last operation. If so, exit.
// As the program has not fully started yet, Shutdown() is possibly overkill.
if (ShutdownRequested(node)) {
LogPrintf("Shutdown requested. Exiting.\n");
return false;
}
ChainstateManager& chainman = *Assert(node.chainman);
assert(!node.peerman);
node.peerman = PeerManager::make(*node.connman, *node.addrman,
node.banman.get(), chainman,
*node.mempool, peerman_opts);
RegisterValidationInterface(node.peerman.get());
// ********************************************************* Step 8: start indexers
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
g_txindex = std::make_unique<TxIndex>(interfaces::MakeChain(node), cache_sizes.tx_index, false, fReindex);
node.indexes.emplace_back(g_txindex.get());
}
for (const auto& filter_type : g_enabled_filter_types) {
InitBlockFilterIndex([&]{ return interfaces::MakeChain(node); }, filter_type, cache_sizes.filter_index, false, fReindex);
node.indexes.emplace_back(GetBlockFilterIndex(filter_type));
}
if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) {
g_coin_stats_index = std::make_unique<CoinStatsIndex>(interfaces::MakeChain(node), /*cache_size=*/0, false, fReindex);
node.indexes.emplace_back(g_coin_stats_index.get());
}
// Init indexes
for (auto index : node.indexes) if (!index->Init()) return false;
// ********************************************************* Step 9: load wallet
for (const auto& client : node.chain_clients) {
if (!client->load()) {
return false;
}
}
// ********************************************************* Step 10: data directory maintenance
// if pruning, perform the initial blockstore prune
// after any wallet rescanning has taken place.
if (chainman.m_blockman.IsPruneMode()) {
if (!fReindex) {
LOCK(cs_main);
for (Chainstate* chainstate : chainman.GetAll()) {
uiInterface.InitMessage(_("Pruning blockstore…").translated);
chainstate->PruneAndFlush();
}
}
} else {
LogPrintf("Setting NODE_NETWORK on non-prune mode\n");
nLocalServices = ServiceFlags(nLocalServices | NODE_NETWORK);
}
// ********************************************************* Step 11: import blocks
if (!CheckDiskSpace(args.GetDataDirNet())) {
InitError(strprintf(_("Error: Disk space is low for %s"), fs::quoted(fs::PathToString(args.GetDataDirNet()))));
return false;
}
if (!CheckDiskSpace(args.GetBlocksDirPath())) {
InitError(strprintf(_("Error: Disk space is low for %s"), fs::quoted(fs::PathToString(args.GetBlocksDirPath()))));
return false;
}
int chain_active_height = WITH_LOCK(cs_main, return chainman.ActiveChain().Height());
// On first startup, warn on low block storage space
if (!fReindex && !fReindexChainState && chain_active_height <= 1) {
uint64_t assumed_chain_bytes{chainparams.AssumedBlockchainSize() * 1024 * 1024 * 1024};
uint64_t additional_bytes_needed{
chainman.m_blockman.IsPruneMode() ?
std::min(chainman.m_blockman.GetPruneTarget(), assumed_chain_bytes) :
assumed_chain_bytes};
if (!CheckDiskSpace(args.GetBlocksDirPath(), additional_bytes_needed)) {
InitWarning(strprintf(_(
"Disk space for %s may not accommodate the block files. " \
"Approximately %u GB of data will be stored in this directory."
),
fs::quoted(fs::PathToString(args.GetBlocksDirPath())),
chainparams.AssumedBlockchainSize()
));
}
}
// Either install a handler to notify us when genesis activates, or set fHaveGenesis directly.
// No locking, as this happens before any background thread is started.
boost::signals2::connection block_notify_genesis_wait_connection;
if (WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip() == nullptr)) {
block_notify_genesis_wait_connection = uiInterface.NotifyBlockTip_connect(std::bind(BlockNotifyGenesisWait, std::placeholders::_2));
} else {
fHaveGenesis = true;
}
#if HAVE_SYSTEM
const std::string block_notify = args.GetArg("-blocknotify", "");
if (!block_notify.empty()) {
uiInterface.NotifyBlockTip_connect([block_notify](SynchronizationState sync_state, const CBlockIndex* pBlockIndex) {
if (sync_state != SynchronizationState::POST_INIT || !pBlockIndex) return;
std::string command = block_notify;
ReplaceAll(command, "%s", pBlockIndex->GetBlockHash().GetHex());
std::thread t(runCommand, command);
t.detach(); // thread runs free
});
}
#endif
std::vector<fs::path> vImportFiles;
for (const std::string& strFile : args.GetArgs("-loadblock")) {
vImportFiles.push_back(fs::PathFromString(strFile));
}
chainman.m_thread_load = std::thread(&util::TraceThread, "initload", [=, &chainman, &args, &node] {
// Import blocks
ImportBlocks(chainman, vImportFiles);
if (args.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
LogPrintf("Stopping after block import\n");
if (!(*Assert(node.shutdown))()) {
LogPrintf("Error: failed to send shutdown signal after finishing block import\n");
}
return;
}
// Start indexes initial sync
if (!StartIndexBackgroundSync(node)) {
bilingual_str err_str = _("Failed to start indexes, shutting down..");
chainman.GetNotifications().fatalError(err_str.original, err_str);
return;
}
// Load mempool from disk
if (auto* pool{chainman.ActiveChainstate().GetMempool()}) {
LoadMempool(*pool, ShouldPersistMempool(args) ? MempoolPath(args) : fs::path{}, chainman.ActiveChainstate(), {});
pool->SetLoadTried(!chainman.m_interrupt);
}
});
// Wait for genesis block to be processed
{
WAIT_LOCK(g_genesis_wait_mutex, lock);
// We previously could hang here if shutdown was requested prior to
// ImportBlocks getting started, so instead we just wait on a timer to
// check ShutdownRequested() regularly.
while (!fHaveGenesis && !ShutdownRequested(node)) {
g_genesis_wait_cv.wait_for(lock, std::chrono::milliseconds(500));
}
block_notify_genesis_wait_connection.disconnect();
}
if (ShutdownRequested(node)) {
return false;
}
// ********************************************************* Step 12: start node
debug print
{
LOCK(cs_main);
LogPrintf("block tree size = %u\n", chainman.BlockIndex().size());
chain_active_height = chainman.ActiveChain().Height();
if (tip_info) {
tip_info->block_height = chain_active_height;
tip_info->block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : chainman.GetParams().GenesisBlock().GetBlockTime();
tip_info->verification_progress = GuessVerificationProgress(chainman.GetParams().TxData(), chainman.ActiveChain().Tip());
}
if (tip_info && chainman.m_best_header) {
tip_info->header_height = chainman.m_best_header->nHeight;
tip_info->header_time = chainman.m_best_header->GetBlockTime();
}
}
LogPrintf("nBestHeight = %d\n", chain_active_height);
if (node.peerman) node.peerman->SetBestHeight(chain_active_height);
// Map ports with UPnP or NAT-PMP.
StartMapPort(args.GetBoolArg("-upnp", DEFAULT_UPNP), args.GetBoolArg("-natpmp", DEFAULT_NATPMP));
CConnman::Options connOptions;
connOptions.nLocalServices = nLocalServices;
connOptions.m_max_automatic_connections = nMaxConnections;
connOptions.uiInterface = &uiInterface;
connOptions.m_banman = node.banman.get();
connOptions.m_msgproc = node.peerman.get();
connOptions.nSendBufferMaxSize = 1000 * args.GetIntArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER);
connOptions.nReceiveFloodSize = 1000 * args.GetIntArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER);
connOptions.m_added_nodes = args.GetArgs("-addnode");
connOptions.nMaxOutboundLimit = *opt_max_upload;
connOptions.m_peer_connect_timeout = peer_connect_timeout;
// Port to bind to if `-bind=addr` is provided without a `:port` suffix.
const uint16_t default_bind_port =
static_cast<uint16_t>(args.GetIntArg("-port", Params().GetDefaultPort()));
const auto BadPortWarning = [](const char* prefix, uint16_t port) {
return strprintf(_("%s request to listen on port %u. This port is considered \"bad\" and "
"thus it is unlikely that any peer will connect to it. See "
"doc/p2p-bad-ports.md for details and a full list."),
prefix,
port);
};
for (const std::string& bind_arg : args.GetArgs("-bind")) {
std::optional<CService> bind_addr;
const size_t index = bind_arg.rfind('=');
if (index == std::string::npos) {
bind_addr = Lookup(bind_arg, default_bind_port, /*fAllowLookup=*/false);
if (bind_addr.has_value()) {
connOptions.vBinds.push_back(bind_addr.value());
if (IsBadPort(bind_addr.value().GetPort())) {
InitWarning(BadPortWarning("-bind", bind_addr.value().GetPort()));
}
continue;
}
} else {
const std::string network_type = bind_arg.substr(index + 1);
if (network_type == "onion") {
const std::string truncated_bind_arg = bind_arg.substr(0, index);
bind_addr = Lookup(truncated_bind_arg, BaseParams().OnionServiceTargetPort(), false);
if (bind_addr.has_value()) {
connOptions.onion_binds.push_back(bind_addr.value());
continue;
}
}
}
return InitError(ResolveErrMsg("bind", bind_arg));
}
for (const std::string& strBind : args.GetArgs("-whitebind")) {
NetWhitebindPermissions whitebind;
bilingual_str error;
if (!NetWhitebindPermissions::TryParse(strBind, whitebind, error)) return InitError(error);
connOptions.vWhiteBinds.push_back(whitebind);
}
// If the user did not specify -bind= or -whitebind= then we bind
// on any address - 0.0.0.0 (IPv4) and :: (IPv6).
connOptions.bind_on_any = args.GetArgs("-bind").empty() && args.GetArgs("-whitebind").empty();
// Emit a warning if a bad port is given to -port= but only if -bind and -whitebind are not
// given, because if they are, then -port= is ignored.
if (connOptions.bind_on_any && args.IsArgSet("-port")) {
const uint16_t port_arg = args.GetIntArg("-port", 0);
if (IsBadPort(port_arg)) {
InitWarning(BadPortWarning("-port", port_arg));
}
}
CService onion_service_target;
if (!connOptions.onion_binds.empty()) {
onion_service_target = connOptions.onion_binds.front();
} else {
onion_service_target = DefaultOnionServiceTarget();
connOptions.onion_binds.push_back(onion_service_target);
}
if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) {
if (connOptions.onion_binds.size() > 1) {
InitWarning(strprintf(_("More than one onion bind address is provided. Using %s "
"for the automatically created Tor onion service."),
onion_service_target.ToStringAddrPort()));
}
StartTorControl(onion_service_target);
}
if (connOptions.bind_on_any) {
// Only add all IP addresses of the machine if we would be listening on
// any address - 0.0.0.0 (IPv4) and :: (IPv6).
Discover();
}
for (const auto& net : args.GetArgs("-whitelist")) {
NetWhitelistPermissions subnet;
bilingual_str error;
if (!NetWhitelistPermissions::TryParse(net, subnet, error)) return InitError(error);
connOptions.vWhitelistedRange.push_back(subnet);
}
connOptions.vSeedNodes = args.GetArgs("-seednode");
// Initiate outbound connections unless connect=0
connOptions.m_use_addrman_outgoing = !args.IsArgSet("-connect");
if (!connOptions.m_use_addrman_outgoing) {
const auto connect = args.GetArgs("-connect");
if (connect.size() != 1 || connect[0] != "0") {
connOptions.m_specified_outgoing = connect;
}
if (!connOptions.m_specified_outgoing.empty() && !connOptions.vSeedNodes.empty()) {
LogPrintf("-seednode is ignored when -connect is used\n");
}
if (args.IsArgSet("-dnsseed") && args.GetBoolArg("-dnsseed", DEFAULT_DNSSEED) && args.IsArgSet("-proxy")) {
LogPrintf("-dnsseed is ignored when -connect is used and -proxy is specified\n");
}
}
const std::string& i2psam_arg = args.GetArg("-i2psam", "");
if (!i2psam_arg.empty()) {
const std::optional<CService> addr{Lookup(i2psam_arg, 7656, fNameLookup)};
if (!addr.has_value() || !addr->IsValid()) {
return InitError(strprintf(_("Invalid -i2psam address or hostname: '%s'"), i2psam_arg));
}
SetProxy(NET_I2P, Proxy{addr.value()});
} else {
if (args.IsArgSet("-onlynet") && g_reachable_nets.Contains(NET_I2P)) {
return InitError(
_("Outbound connections restricted to i2p (-onlynet=i2p) but "
"-i2psam is not provided"));
}
g_reachable_nets.Remove(NET_I2P);
}
connOptions.m_i2p_accept_incoming = args.GetBoolArg("-i2pacceptincoming", DEFAULT_I2P_ACCEPT_INCOMING);
if (!node.connman->Start(*node.scheduler, connOptions)) {
return false;
}
// ********************************************************* Step 13: finished
// At this point, the RPC is "started", but still in warmup, which means it
// cannot yet be called. Before we make it callable, we need to make sure
// that the RPC's view of the best block is valid and consistent with
// ChainstateManager's active tip.
//
// If we do not do this, RPC's view of the best block will be height=0 and
// hash=0x0. This will lead to erroroneous responses for things like
// waitforblockheight.
RPCNotifyBlockChange(WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()));
SetRPCWarmupFinished();
uiInterface.InitMessage(_("Done loading").translated);
for (const auto& client : node.chain_clients) {
client->start(*node.scheduler);
}
BanMan* banman = node.banman.get();
node.scheduler->scheduleEvery([banman]{
banman->DumpBanlist();
}, DUMP_BANS_INTERVAL);
if (node.peerman) node.peerman->StartScheduledTasks(*node.scheduler);
#if HAVE_SYSTEM
StartupNotify(args);
#endif
return true;
}
首先关键代码进行一个逐行解析
- 函数定义
bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
这个函数接受两个参数:一个 NodeContext 引用和一个指向 BlockAndHeaderTipInfo 的指针。NodeContext 包含了节点的上下文信息,BlockAndHeaderTipInfo 用于存储区块链的尖端信息。函数返回一个布尔值,表示初始化是否成功。
- 参数解析和链参数获取
const ArgsManager& args = *Assert(node.args);
const CChainParams& chainparams = Params();
这里从节点上下文中获取命令行参数和区块链参数。
- 上传目标的解析
auto opt_max_upload = ParseByteUnits(args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET), ByteUnit::M);
if (!opt_max_upload) {
return InitError(strprintf(_("Unable to parse -maxuploadtarget: '%s'"), args.GetArg("-maxuploadtarget", "")));
}
解析 -maxuploadtarget 参数,这是节点最大上传限制。如果解析失败,函数会返回错误并退出。
- 应用初始化(步骤 4a)
if (!CreatePidFile(args)) {
// Detailed error printed inside CreatePidFile().
return false;
}
if (!init::StartLogging(args)) {
// Detailed error printed inside StartLogging().
return false;
}
创建 PID 文件,以确保不会启动多个实例。然后初始化日志记录系统。如果任何一个失败,函数将返回错误。
- 配置警告
if (args.IsArgSet("-datadir") && !args.GetPathArg("-datadir").is_absolute()) {
// ... (打印警告信息)
}
如果设置了 -datadir 参数并且它是一个相对路径,则打印一条警告信息。
- 缓存和内存分配
ValidationCacheSizes validation_cache_sizes{};
ApplyArgsManOptions(args, validation_cache_sizes);
if (!InitSignatureCache(validation_cache_sizes.signature_cache_bytes)
|| !InitScriptExecutionCache(validation_cache_sizes.script_execution_cache_bytes))
{
// ... (返回初始化错误)
}
初始化签名缓存和脚本执行缓存,如果初始化失败,则返回错误。
- 定时任务的调度
assert(!node.scheduler);
node.scheduler = std::make_unique<CScheduler>();
断言调度器未初始化,然后创建一个新的调度器。
node.scheduler->m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { node.scheduler->serviceQueue(); });
启动一个线程来运行调度器的服务队列。
node.scheduler->scheduleEvery([]{
RandAddPeriodic();
}, std::chrono::minutes{1});
每分钟向随机数生成器添加熵。
node.scheduler->scheduleEvery([&args, &node]{
// ... (检查磁盘空间)
}, std::chrono::minutes{5});
每五分钟检查一次磁盘空间。
- 客户端接口和 RPC 命令注册
g_wallet_init_interface.Construct(node);
uiInterface.InitWallet();
RegisterAllCoreRPCCommands(tableRPC);
// ... (注册其他 RPC 命令)
初始化钱包接口,注册核心 RPC 命令。
- RPC 服务器初始化
if (args.GetBoolArg("-server", false)) {
// ... (启动 RPC 服务器)
}
如果设置了 -server 参数,则启动 RPC 服务器。
- 钱包数据库完整性检查
for (const auto& client : node.chain_clients) {
if (!client->verify()) {
return false;
}
}
遍历所有链客户端,检查它们的钱包数据库完整性。
- 网络初始化(步骤 6)
fListen = args.GetBoolArg("-listen", DEFAULT_LISTEN);
fDiscover = args.GetBoolArg("-discover", true);
设置是否监听网络连接和是否启用地址发现。
- 磁盘空间检查
if (!CheckDiskSpace(args.GetBlocksDirPath(), min_disk_space)) {
// ... (打印警告并尝试关闭)
}
检查区块数据目录的磁盘空间。
- 加载区块链(步骤 7)
node.notifications = std::make_unique<KernelNotifications>(*Assert(node.shutdown), node.exit_status);
// ... (加载区块链相关的代码)
创建内核通知系统,然后加载区块链数据。
- 启动索引器和加载钱包
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
// ... (初始化交易索引器)
}
// ... (加载其他索引器和钱包)
如果启用了 -txindex,则初始化交易索引器。之后加载其他索引器和钱包。
- 数据目录维护和区块导入
if (chainman.m_blockman.IsPruneMode()) {
// ... (如果启用了修剪模式,执行区块存储修剪)
} else {
// ... (设置 NODE_NETWORK 服务标志)
}
如果启用了区块存储修剪模式,则执行修剪。否则,设置网络节点服务标志。
- 启动节点(步骤 12)
StartMapPort(args.GetBoolArg("-upnp", DEFAULT_UPNP), args.GetBoolArg("-natpmp", DEFAULT_NATPMP));
使用 UPnP 或 NAT-PMP 映射端口。
if (!node.connman->Start(*node.scheduler, connOptions)) {
return false;
}
启动网络连接管理器。
- 完成初始化(步骤 13)
SetRPCWarmupFinished();
uiInterface.InitMessage(_("Done loading").translated);
设置 RPC 预热完成,显示初始化完成的消息。
- 各种定时任务
BanMan* banman = node.banman.get();
node.scheduler->scheduleEvery([banman]{
banman->DumpBanlist();
}, DUMP_BANS_INTERVAL);
定期将封禁列表保存到磁盘。
- 启动后通知
#if HAVE_SYSTEM
StartupNotify(args);
#endif
如果系统支持,执行启动后的通知。
这个函数涉及到的操作非常多,包括配置解析、日志记录、网络服务、RPC 接口、钱包加载、索引器启动、区块链数据加载等。每个操作都可能失败并导致整个函数返回错误。这是一个节点启动时非常关键的部分,确保所有的服务和组件都正确初始化。
以下是对代码进行超详细的解读
1. 参数解析和错误处理
代码的开始部分是关于解析命令行参数。首先,它尝试解析 -maxuploadtarget 参数,这个参数限制了节点的最大上传流量。如果参数无法被正确解析,函数会立即返回一个错误。
2. 应用程序初始化
接下来,函数尝试创建一个PID文件,以防止启动多个实例。然后,它开始日志记录系统。如果任何一个步骤失败,函数都会返回错误。
3. 配置警告
函数检查 -datadir 参数是否设置为相对路径。如果是,它会记录一条警告,因为相对路径可能会导致数据目录在不同的工作目录下无法找到。
4. 缓存和内存分配
函数初始化签名缓存和脚本执行缓存,这些缓存用于优化验证过程。如果缓存无法被分配,函数会返回一个初始化错误。
5. 调度器和周期性任务
函数创建一个调度器实例并启动一个新线程来管理周期性任务,如随机数生成器的熵添加和磁盘空间检查。
6. 客户端接口和RPC命令注册
此部分构造了钱包接口,并注册了RPC命令,以便即使在禁用服务器功能的情况下也能在GUI RPC控制台中使用这些命令。
7. RPC服务器初始化
如果启用了RPC服务器(通过 -server 参数),则函数将初始化RPC服务器并设置为预热模式,此时服务器不会处理调用。
8. 钱包数据库完整性检查
对所有配置的钱包进行完整性检查,以确保它们没有损坏。
9. 网络初始化
配置网络相关的选项,如监听设置、对等网络管理选项,并处理与网络地址映射相关的参数。
10. 区块链加载
此部分涉及到区块链的初始化,包括加载区块索引、验证链状态、初始化交易池和启动区块链索引器。
11. 数据目录维护和区块导入
如果节点配置为修剪模式,此步骤会进行区块存储的修剪。否则,它会设置节点服务标志。然后,它检查磁盘空间并导入任何通过 -loadblock 参数指定的区块文件。
12. 启动节点
此步骤中,节点开始映射端口,发现网络地址,启动网络连接管理器,接受传入连接,并开始与其他节点的通信。
13. 完成初始化
最后,函数完成RPC的预热,向用户通知节点已完成加载,并启动钱包客户端。
14. 定时任务
设置定时任务,如定期将封禁列表保存到磁盘。
整个 AppInitMain 函数是节点启动过程中最重要的部分之一,它负责设置和验证所有必要的组件和服务。如果任何步骤失败,函数将返回错误,并且节点不会启动。这确保了节点在开始其核心功能之前处于一致和安全的状态。
15. 钱包加载
如果用户配置了钱包功能,AppInitMain 将负责加载钱包。这包括读取钱包文件、验证其完整性、以及(如果需要)执行钱包升级。钱包加载失败通常会导致应用程序启动失败,因为用户的资金安全是最高优先级。
16. 节点服务
节点启动后,AppInitMain 函数将确保节点开始提供网络服务。这包括监听传入的连接、尝试与网络中的其他节点建立连接、以及开始同步区块链数据。网络服务是节点功能的核心,不仅包括区块链数据的传播,还包括交易的传播。
17. 矿工启动(如果配置)
如果节点配置为挖矿,AppInitMain 还将负责启动挖矿进程。这通常涉及到配置挖矿硬件、设置挖矿参数和启动挖矿线程。
18. 插件或外部服务
在某些实现中,AppInitMain 可能还会负责启动与比特币节点集成的插件或外部服务。这些服务可能包括专门的交易索引器、钱包服务或其他区块链分析工具。
19. 信号处理和关机准备
AppInitMain 还会设置信号处理,以便在接收到如 SIGINT 或 SIGTERM 等信号时能够优雅地关闭节点。它还会准备好关机逻辑,以确保在收到关闭命令时,所有组件和服务都能够安全地关闭,不会导致数据丢失或损坏。
20. 性能优化
最后,AppInitMain 可能还会根据用户的配置和系统的能力对节点进行性能优化。这可能包括内存使用的调整、数据库缓存的配置、线程池的大小设置等。
整个初始化过程的目标是确保节点在进入其主操作循环之前,已经处于最佳状态,并且所有必要的服务都已经处于在线状态。这个过程是复杂的,因为它必须考虑到多种配置选项、可能的错误情况以及与系统资源的交互。此外,因为比特币节点通常处理价值较高的资产,所以安全性和稳定性是设计和实现中的首要考虑因素。
在实际的比特币客户端实现中,AppInitMain 函数的具体内容可能会有所不同,因为开发者可能会根据新的网络条件、安全发现或性能需求对其进行调整。但是,上述概述的步骤提供了一个框架,说明了比特币节点启动时需要完成的任务类型。