bitget

bitget交易所

Bitget是一家总部位于新加坡的全球化金融交易平台,凭借资深专业团队和安全稳定的技术引擎脱颖而出。以合法、合规为原则,覆盖全球市场,提供包括比特币,以太坊等丰富的数字资产交易服务。致力于为用户提供安全专业的金融创新和增值服务。

bitcoin代码分析(六)

bitget资讯 {author} 2024-04-08 08:35:43 1

今天来详细解读比特币源代码: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(); // 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 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(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(args.GetDataDirNet() / "banlist", &uiInterface, args.GetIntArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); assert(!node.connman); node.connman = std::make_unique(GetRand(), GetRand(), *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(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 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 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 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 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(*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(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(mempool_opts); node.chainman = std::make_unique(*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 dms\n", Ticks(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(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(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 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(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 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 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();

断言调度器未初始化,然后创建一个新的调度器。

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(*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_SYSTEMStartupNotify(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 函数的具体内容可能会有所不同,因为开发者可能会根据新的网络条件、安全发现或性能需求对其进行调整。但是,上述概述的步骤提供了一个框架,说明了比特币节点启动时需要完成的任务类型。

bitcoin代码分析(六)文档下载: PDF DOC TXT

上一篇:“区块链技术”和“比特币”的渊源

下一篇:一年内蒸发8000亿人民币,以太坊矿工还挖矿吗?简直不要太机智

猜你喜欢