Repository: bitcoin/bitcoin
File: src/init.cpp (C++)

Code before:
            } else {
                // Same service, different permissions - conflict!
                return wb.m_service;
            }
        }
        vWhiteBinds = std::move(deduplicated);
    }

    // Step 2: Simple deduplication for vBinds
    {
        std::set<CService> seen;
        auto it = std::remove_if(vBinds.begin(), vBinds.end(),
            [&seen](const CService& service) {
                return !seen.insert(service).second;  // Remove if already seen
            });
        vBinds.erase(it, vBinds.end());
    }

    // Step 3: Simple deduplication for onion_binds
    {
        std::set<CService> seen;
        auto it = std::remove_if(onion_binds.begin(), onion_binds.end(),
            [&seen](const CService& service) {
                return !seen.insert(service).second;  // Remove if already seen
            });
        onion_binds.erase(it, onion_binds.end());
    }

    // Step 4: Check for cross-vector conflicts
    // Build sets for efficient lookup
    std::set<CService> whitebind_services;
    for (const auto& wb : vWhiteBinds) {
        whitebind_services.insert(wb.m_service);
    }

    // Check if any service from vBinds exists in vWhiteBinds
    for (const auto& bind : vBinds) {
        if (whitebind_services.count(bind)) {
            return bind;  // Conflict between vBinds and vWhiteBinds
        }
    }

    // Check if any service from onion_binds exists in vWhiteBinds or vBinds
    std::set<CService> bind_services(vBinds.begin(), vBinds.end());
    for (const auto& onion_bind : onion_binds) {
        if (whitebind_services.count(onion_bind)) {
            return onion_bind;  // Conflict between onion_binds and vWhiteBinds
        }
        if (bind_services.count(onion_bind)) {
            return onion_bind;  // Conflict between onion_binds and vBinds
        }

Review comment (refactor):
I find it a bit inconsistent that we're not erring here - we don't know why they provided the same thing twice, seems like a mistake, not sure we should attempt to correct it silently. Would also simplify the code if we unified the way we handle these

Code after:
}

// A GUI user may opt to retry once with do_reindex set if there is a failure during chainstate initialization.
// The function therefore has to support re-entry.
static ChainstateLoadResult InitAndLoadChainstate(
    NodeContext& node,
    bool do_reindex,
    const bool do_reindex_chainstate,
    const kernel::CacheSizes& cache_sizes,
    const ArgsManager& args)
{
    // This function may be called twice, so any dirty state must be reset.
    node.notifications.reset(); // Drop state, such as a cached tip block
    node.mempool.reset();
    node.chainman.reset(); // Drop state, such as an initialized m_block_tree_db

    const CChainParams& chainparams = Params();

    Assert(!node.notifications); // Was reset above
    node.notifications = std::make_unique<KernelNotifications>(Assert(node.shutdown_request), node.exit_status, *Assert(node.warnings));
    ReadNotificationArgs(args, *node.notifications);

    CTxMemPool::Options mempool_opts{
        .check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0,
        .signals = node.validation_signals.get(),
    };
    Assert(ApplyArgsManOptions(args, chainparams, mempool_opts)); // no error can happen, already checked in AppInitParameterInteraction
    bilingual_str mempool_error;
    Assert(!node.mempool); // Was reset above
    node.mempool = std::make_unique<CTxMemPool>(mempool_opts, mempool_error);
    if (!mempool_error.empty()) {
        return {ChainstateLoadStatus::FAILURE_FATAL, mempool_error};
    }
    LogInfo("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)",
            cache_sizes.coins * (1.0 / 1024 / 1024),
            mempool_opts.max_size_bytes * (1.0 / 1024 / 1024));
    ChainstateManager::Options chainman_opts{
        .chainparams = chainparams,
        .datadir = args.GetDataDirNet(),
        .notifications = *node.notifications,
        .signals = node.validation_signals.get(),
    };
    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,
        .block_tree_db_params = DBParams{
            .path = args.GetDataDirNet() / "blocks" / "index",
            .cache_bytes = cache_sizes.block_tree_db,