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

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐