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

Code before:
                return InitError(InvalidPortErrMsg(param_name, param_value));
#endif
            }
        }
    }

    return true;
}

/**
 * @brief Deduplicates binding vectors internally and checks for cross-vector conflicts.
 *
 * For vWhiteBinds: Removes duplicates with identical service AND permissions, but reports
 * conflicts when same service has different permissions.
 * For other vectors: Simply removes duplicates.
 * Cross-vector duplicates are always reported as conflicts.
 *
 * @param vWhiteBinds Vector of whitelisted bind permissions (will be deduplicated)
 * @param vBinds Vector of regular bind services (will be deduplicated)
 * @param onion_binds Vector of onion-specific bind services (will be deduplicated)
 * @return std::optional<CService> containing the first conflict found, or std::nullopt if no conflicts
 */
std::optional<CService> DeduplicateAndCheckBindingConflicts(
    std::vector<NetWhitebindPermissions>& vWhiteBinds,
    std::vector<CService>& vBinds,
    std::vector<CService>& onion_binds)
{
    // Step 1: Handle vWhiteBinds deduplication with special logic for permissions
    {
        std::map<CService, NetPermissionFlags> seen_whitebinds;
        std::vector<NetWhitebindPermissions> deduplicated;

        for (const auto& wb : vWhiteBinds) {
            auto it = seen_whitebinds.find(wb.m_service);
            if (it == seen_whitebinds.end()) {
                // First occurrence of this service
                seen_whitebinds[wb.m_service] = wb.m_flags;
                deduplicated.push_back(wb);
            } else if (it->second == wb.m_flags) {
                // Same service, same permissions - harmless duplicate, skip it
                continue;
            } else {
                // Same service, different permissions - conflict!
                return wb.m_service;
            }
        }
        vWhiteBinds = std::move(deduplicated);
    }

    // Step 2: Simple deduplication for vBinds
    {

Review comment (refactor):
output parameters are quite hard to reason about - given that the call-site assigns `connOptions` directly, can we either split this into 2-3 helper methods that return the new values (the vectors are tiny, they're likely as fast as passing it around), or if you insist on this big one, can we pass a single `CConnman::Options& connOptions` instead?
Also, some branches return, others don't erase, others replace one of the inputs - seems like has more than a single, easily defined responsibility (the name even has an `And` in the name), I personally would prefer doing the same in more focused methods.

Code after:
                return InitError(InvalidPortErrMsg(param_name, param_value));
#endif
            }
        }
    }

    return true;
}

/**
 * @brief Checks for duplicate bindings across all binding configurations.
 *
 * @param[in] conn_options Connection options containing the binding vectors to check
 * @return std::optional<CService> containing the first duplicate found, or std::nullopt if no duplicates
 */
static std::optional<CService> CheckBindingConflicts(const CConnman::Options& conn_options)
{
    std::set<CService> seen;

    // Check all whitelisted bindings
    for (const auto& wb : conn_options.vWhiteBinds) {
        if (!seen.insert(wb.m_service).second) {
            return wb.m_service;
        }
    }

    // Check regular bindings
    for (const auto& bind : conn_options.vBinds) {
        if (!seen.insert(bind).second) {
            return bind;
        }
    }

    // Check onion bindings
    for (const auto& onion_bind : conn_options.onion_binds) {
        if (!seen.insert(onion_bind).second) {
            return onion_bind;
        }
    }

    return std::nullopt;
}

// 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)