Skip to content

net: Prevent node from binding to the same CService#33231

Merged
achow101 merged 1 commit into
bitcoin:masterfrom
w0xlt:mulitple_binds
Sep 9, 2025
Merged

net: Prevent node from binding to the same CService#33231
achow101 merged 1 commit into
bitcoin:masterfrom
w0xlt:mulitple_binds

Conversation

@w0xlt

@w0xlt w0xlt commented Aug 20, 2025

Copy link
Copy Markdown
Contributor

Currently, if the node inadvertently starts with repeated -bind options (e.g. ./build/bin/bitcoind -listen -bind=0.0.0.0 -bind=0.0.0.0), the user will receive a misleading message followed by the node shutdown:

[net:error] Unable to bind to 0.0.0.0:8333 on this computer. Bitcoin Core is probably already running.
[error] Unable to bind to 0.0.0.0:8333 on this computer. Bitcoin Core is probably already running.

And the user might spend some time looking for a bitcoind process or what application is using port 8333, when what happens is that Bitcoin Core successfully connected to port 8333 and then tries again, generating this fatal error.

This PR proposes that repeated -bind options have no effect.

@DrahtBot DrahtBot added the P2P label Aug 20, 2025
@DrahtBot

DrahtBot commented Aug 20, 2025

Copy link
Copy Markdown
Contributor

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33231.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK l0rinc, yuvicc, vasild, naiyoma, sipa, achow101
Concept ACK jonatack, cedwies

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #33324 (RFC: blocks: add -reobfuscate-blocks arg to xor existing blk/rev on startup by l0rinc)
  • #29415 (Broadcast own transactions only via short-lived Tor or I2P connections by vasild)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@jonatack jonatack left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

This PR proposes that repeated -bind options have no effect.

Only no effect for repeated -bind options having the same value, yes? Would it be preferable to give the user feedback instead?

Would be good to add test coverage.

Comment thread src/net.h Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would need to update the doxygen ("A vector...")

@yuvicc

yuvicc commented Aug 21, 2025

Copy link
Copy Markdown
Contributor

Concept ACK

@l0rinc l0rinc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK, but approach NACK 132a59698e2df2e25fdbae3a6fba308b131419d2

I only did a code review pass on it, please let me know if my concerns are off, happy to ack if I was mistaken.
My biggest concern currently is silently dropping values by partial duplication, e.g. -whitebind=[::1]:8335=relay -whitebind=[::1]:8335=relay,addr.

Comment thread src/net.h Outdated
Comment on lines 1071 to 1072

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the v prefix doesn't make a lot of sense anymore, if they're not vectors

Comment thread src/net.h Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I'd appreciate a test that checks what happens in this case, as @jonatack also mentioned
  • a release‑notes line under P2P noting that repeated -bind and -whitebind arguments are now deduplicated

Comment thread src/net_permissions.h Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't it confusing that in case of service duplicates the it's not obvious which m_flags is ignored?

What do we expect to happen in case of something like -whitebind=127.0.0.1:8335=relay,noban -whitebind=127.0.0.1:8335=relay vs -whitebind=127.0.0.1:8335=relay -whitebind=127.0.0.1:8335=relay,noban?
Or even worse, -whitebind=127.0.0.1:8335=all -whitebind=127.0.0.1:8335=relay,mempool?

Comment thread src/net_permissions.h Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could modernize this to C++20

std::weak_ordering operator<=>(NetWhitebindPermissions const& other) const

nit: formatting is off

@cedwies

cedwies commented Aug 22, 2025

Copy link
Copy Markdown
Contributor

Concept ACK

One point to consider is a behavioral change in how the onion_service_target is selected.
Consider:

-bind=192.167.1.10:8333=onion
-bind=127.0.0.1:8333=onion

Currently 192.167.1.10:8333 will be selected as onion_service_target. With this PR, it will be changed to 127.0.0.1:8333 (though one line in logs shows which address is used as target).

One option would be to keep using sets for deduplication, while still preserving the historical “first specified wins” rule when selecting onion_service_target. That way dedup works as intended without changing existing semantics, and it matches operator expectations (since users may typically think in terms of argument order, not CService’s comparator). On the other hand, one could argue that relying on argument order is fragile, and that making the selection order-independent is preferable. Any thoughts?

@l0rinc

l0rinc commented Aug 22, 2025

Copy link
Copy Markdown
Contributor

Since these lists are tiny, Instead of a set for deduplication, we might as well use a simple O^2 algorithm, iterate from the back and remove whatever appeared in the front (it will likely be even faster than a set). If you decide to do that, I'd appreciate a randomized unit test checking it against a set deduplication.

@w0xlt w0xlt force-pushed the mulitple_binds branch 2 times, most recently from afcafcf to 93d373a Compare August 24, 2025 05:32
@DrahtBot

Copy link
Copy Markdown
Contributor

🚧 At least one of the CI tasks failed.
Task lint: https://github.com/bitcoin/bitcoin/runs/48752053890
LLM reason (✨ experimental): The CI failure is caused by errors detected by the linter ruff, specifically two fixable issues in Python code: missing trailing newline and an f-string without placeholders.

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@w0xlt

w0xlt commented Aug 24, 2025

Copy link
Copy Markdown
Contributor Author

Thank you all for the reviews.
I completely changed the approach.

While addressing the reviews, I realized that using std::set to avoid duplicates doesn't solve the problem. If the options with same address and port but different network like -bind=127.0.0.1:8333 -bind=127.0.0.1:8333=onion are present, the node will crash anyway, as one option will be allocated in the CConnman::Options::vBinds vector and the other in CConnman::Options::onion_binds.

Therefore, even after deduplicating each vector, it is still necessary to check for conflicts between vectors.

I used the following approach:
If the duplicate binds are the same (e.g., bind=0.0.0.0 -bind=0.0.0.0), they will be ignored.
But if they are different (e.g., bind=0.0.0.0:10080 -bind=0.0.0.0:10080=onion), the node will be terminated with a clear and specific message (e.g., Different bind settings assigned to address 0.0.0.0:10080), since we cannot know the user's true intent.

I added the tests as requested. For more explanation about the first commit, see #33250.

@l0rinc l0rinc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

I personally would not try to correct the behavior (especially silently), given that it's rare, I would simply give the user a warning or error and let them fix the arguments.

I also think the tests are a bit complicated and more stateful now, I have added a few suggestions, maybe we can simplify based on them.

Comment thread src/init.cpp Outdated

// Check if any service from vBinds exists in vWhiteBinds
for (const auto& bind : vBinds) {
if (whitebind_services.count(bind)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
if (whitebind_services.count(bind)) {
if (whitebind_services.contains(bind)) {

Comment thread src/init.cpp Outdated
Comment on lines +1250 to +1252
std::vector<NetWhitebindPermissions>& vWhiteBinds,
std::vector<CService>& vBinds,
std::vector<CService>& onion_binds)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/init.cpp Outdated
// Step 2: Simple deduplication for vBinds
{
std::set<CService> seen;
auto it = std::remove_if(vBinds.begin(), vBinds.end(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
auto it = std::remove_if(vBinds.begin(), vBinds.end(),
auto it = std::ranges::remove_if(onion_binds,

Comment thread src/init.cpp Outdated
continue;
} else {
// Same service, different permissions - conflict!
return wb.m_service;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we don't expect this to happen often, I guess it's enough to stop at the first error instead of listing all 👍

Comment thread src/init.cpp Outdated
}

// Check if any service from onion_binds exists in vWhiteBinds or vBinds
std::set<CService> bind_services(vBinds.begin(), vBinds.end());

@l0rinc l0rinc Aug 25, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as mentioned before, I think we can safely check vBinds directly - it's simpler and likely even faster than this

Comment thread src/init.cpp Outdated
Comment on lines +1298 to +1251
std::set<CService> whitebind_services;
for (const auto& wb : vWhiteBinds) {
whitebind_services.insert(wb.m_service);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need to recreate this or can we reuse seen_whitebinds or even better, just iterate vWhiteBinds values directly?

Comment thread test/functional/feature_bind_extra.py Outdated
],
)
port += 2
port += 2 # Increment port to avoid conflicts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: in first case increment means +1, here it's +1 - but more generally, the code is already, it's enough if we just document the intent:

Suggested change
port += 2 # Increment port to avoid conflicts
port += 2 # avoid conflicts since we've used two ports before

or even better, why are we even reusing the same variable, why not create a port iterator and instead of the list a lambda accepting two ports something like (untested, since this test is skipped on a Mac):

def run_test(self):
    lp = addr_to_hex("127.0.0.1")
    test_cases = [
        lambda p, _: ([[f"-bind=127.0.0.1:{p}=onion"], # onion-only
                       [(lp, p)], "no normal -bind with -bind=...=onion"]),
        lambda p, q: ([[f"-bind=127.0.0.1:{p}", f"-bind=127.0.0.1:{q}=onion"], # bind + onion
                       [(lp, p), (lp, q)], "both -bind and -bind=...=onion"]),
        lambda p, _: ([[f"-bind=127.0.0.1:{p}"], # bind only
                       [(lp, p)], "no -bind=...=onion"]),
        lambda p, q: ([[f"-bind=127.0.0.1:{p}", f"-bind=127.0.0.1:{p}", f"-bind=127.0.0.1:{q}"], # duplicated bind
                       [(lp, p), (lp, p), (lp, q)], "duplicated -bind=... and -bind=..."]),
        lambda p, q: ([[f"-bind=127.0.0.1:{p}=onion", f"-bind=127.0.0.1:{p}=onion", f"-bind=127.0.0.1:{q}=onion"], # duplicated onion bind
                       [(lp, p), (lp, p), (lp, q)], "duplicated -bind=...=onion and -bind=...=onion"]),
    ]

    ports = itertools.count(p2p_port(self.num_nodes))
    for i, make in enumerate(test_cases):
        args, expected_services, description = make(next(ports), next(ports))
        self.log.info(f"Test case {i + 1}: {description}")
        self.log.info(f"Restarting node 0 with args: {args}")

It's also a bit inconsistent that at the end we just hardcode -bind=127.0.0.1:11012, maybe we could extract that port as well

Comment thread src/init.cpp Outdated
[&seen](const CService& service) {
return !seen.insert(service).second; // Remove if already seen
});
onion_binds.erase(it, onion_binds.end());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/init.cpp Outdated

for (const auto& wb : vWhiteBinds) {
auto it = seen_whitebinds.find(wb.m_service);
if (it == seen_whitebinds.end()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would make sense to invert this if wee keep this format, it makes a bit more sense to use it in the condition where we're checking it (would also enable embedding it in the condition).
Not sure this applies if you remove the reundant deduplication code and use iteration instead of sets. I think that would simplify all of this considerably.

Comment thread test/functional/feature_bind_extra.py Outdated

self.nodes[0].assert_start_raises_init_error(
["-bind=127.0.0.1:11012", "-bind=127.0.0.1:11012=onion"],
"Different binding configurations assigned to the address 127.0.0.1:11012",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: if it's a regex, we need to escape some chars

@w0xlt

w0xlt commented Aug 26, 2025

Copy link
Copy Markdown
Contributor Author

@l0rinc thank you for your detailed reviews.

I personally would not try to correct the behavior (especially silently), given that it's rare, I would simply give the user a warning or error and let them fix the arguments.

I adjusted the approach to meet this requirement. Now when duplicates are detected, the node terminates with a clear, specific error message: "Duplicate binding configuration for address < addr >. Please check your -bind, -whitebind, and onion binding settings."

I don't have a strong opinion on whether the node should try to correct the behavior or not. For me, the main issue is the misleading message.

This version is much more simplified compared to the previous ones

Comment thread src/init.cpp Outdated
connOptions.m_i2p_accept_incoming = args.GetBoolArg("-i2pacceptincoming", DEFAULT_I2P_ACCEPT_INCOMING);

if (auto conflict = CheckBindingConflicts(connOptions)) {
return InitError(Untranslated(strprintf("Duplicate binding configuration for address %s. "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice and clear! One small UX suggestion: since we already know which container the duplicate came from, it could be helpful to mention the categories in the error (e.g. “-bind vs -whitebind”). That said, even without categories, I think this should at least be translatable for consistency with nearby init errors (e.g. i2psam). Something like:

return InitError(strprintf(
    _("Duplicate binding configuration for address %s. "
      "Please check your -bind, -whitebind, and onion binding settings."),
    conflict->ToStringAddrPort()));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we already know which container the duplicate came from

That is known inside CheckBindingConflicts() but the info is not signaled to the caller of the function. Also, even inside the function, only one of the containers is known, the other is not. E.g. if the 3rd for-loop finds a duplicate, then it is unknown whether the entry was added in the first or the second for-loop.

I think this should at least be translatable for consistency with nearby init errors

Right, +1 for translating it.

@w0xlt w0xlt Sep 4, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the info is not signaled to the caller of the function.

Yes, earlier versions of this PR did that, but I simplified it.

I think this should at least be translatable for consistency with nearby init errors

Done in 1c40b32597712059d5d809925da0e9adccac0fb3

@l0rinc l0rinc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this simpler version more, left a few more comments

Now that we're not deduplicating anymore, the title could be adjusted:

net: reject duplicate bind addresses across -bind/-whitebind/onion

The PR desciption also hints that we still do:

This PR proposes that repeated -bind options have no effect.

Comment thread test/functional/feature_bind_extra.py Outdated
self.stop_node(0)

# Test all combinations of duplicate bindings (each should fail)
error_msg = "Error: Duplicate binding configuration for address 127.0.0.1:11012. " \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This seems to be used a single time, we might as well inline it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1c40b32597712059d5d809925da0e9adccac0fb3. Thanks.

Comment thread test/functional/feature_bind_extra.py Outdated
Comment on lines +109 to +110
error_msg,
match=ErrorMatch.FULL_TEXT)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the advantage of duplicating the exact error here, wouldn't this suffice?

Suggested change
error_msg,
match=ErrorMatch.FULL_TEXT)
"Error: Duplicate binding configuration",
match=ErrorMatch.PARTIAL_REGEX)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1c40b32597712059d5d809925da0e9adccac0fb3.

Comment thread test/functional/feature_bind_extra.py Outdated
addr_to_hex,
get_bind_addrs,
)
from test_framework.test_node import ErrorMatch

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc at the top is slightly off now:

"""
Test starting bitcoind with -bind and/or -bind=...=onion, confirm that
it binds to the expected ports, and verify that duplicate or conflicting
-bind/-whitebind configurations are rejected with a descriptive error.
"""

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1c40b32597712059d5d809925da0e9adccac0fb3.

Comment thread test/functional/feature_bind_extra.py Outdated
Comment on lines +104 to +106
for i, opt1 in enumerate(bind_options):
for j, opt2 in enumerate(bind_options):
if i <= j: # Avoid testing reverse duplicates (A,B) and (B,A)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could make this manual iteration more pythony with something like:

from itertools import combinations_with_replacement
addr = f"127.0.0.1:11012"
for opt1, opt2 in combinations_with_replacement([f"-bind={addr}", f"-bind={addr}=onion", f"-whitebind=noban@{addr}"], 2):
    print(f"opt1 = {opt1}, opt2 = {opt2}") # replace with assert, I just used this to validate if the iteration is the same

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1c40b32597712059d5d809925da0e9adccac0fb3.

Comment thread src/init.cpp
Comment on lines +1236 to +1241
/**
* @brief Checks for duplicate bindings across all binding configuration.
*
* @param connOptions Connection options containing the binding vectors to check
* @return std::optional<CService> containing the first duplicate found, or std::nullopt if no duplicates
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally consider these comments noisy - all of this is already evident from the code. If it's not, I'd rather change the code. Seeing the unupdated comment below, we shouldn't just add extra work for our future selves.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't clear to me what the return value is before reading the comment, so this is useful. Sometimes comments are more on the side of redundant/noisy/useless, but the line between that and useful comments is blurred and personally I prefer to err on the side of redundancy.

If there are no comments whatsoever, this sets a standard. Then if the function is extended with not-so-obvious parameters or return value changed, the developer doing it will likely not add a comment for the new parameter.

Yes, comments need to be maintained to be in sync with the code. But note that the argument "Lets omit some comments because we will have to maintain them (more future work)" is missing a point - a code is read many more times than it is modified. So it is better have code that is "easier to read and 'harder' to modify" than "harder to read and easier to modify".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll leave the comment in for now.
I agree that clear, organized code is the best form of documentation, but I think helpful comments can still save valuable developer time.

Comment thread src/init.cpp

connOptions.m_i2p_accept_incoming = args.GetBoolArg("-i2pacceptincoming", DEFAULT_I2P_ACCEPT_INCOMING);

if (auto conflict = CheckBindingConflicts(connOptions)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected that I'm seeing this twice?

2025-08-26T18:23:45Z [error] Duplicate binding configuration for address 127.0.0.1:11012. Please check your -bind, -whitebind, and onion binding settings.
2025-08-26T18:23:45Z tor: Thread interrupt
2025-08-26T18:23:45Z torcontrol thread exit
2025-08-26T18:23:45Z Shutdown in progress...
Error: Duplicate binding configuration for address 127.0.0.1:11012. Please check your -bind, -whitebind, and onion binding settings.

And if we're not actually stopping the execution, shouldn't it rather be a warning?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the first err msg is from logging while the second is from init.cpp. From the comment #33231 (comment), I can see that we would want to stop the execution.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The execution will be stopped either way (in the master branch or in this PR).
This PR simply prevents the node from attempting to connect twice to the same address and port, and instead terminates it with a clear message — rather than the confusing one currently shown in the master branch.

I guess the first err msg is from logging while the second is from init.cpp

Yes, that makes sense. I'll check again.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it just continues IBD on my side after the warning, it doesn't seem to stop after the error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are running in the foreground, InitError() prints the message to the console. In debug.log, only a single entry is recorded.
This behavior applies to all InitError() calls.

uiInterface.ThreadSafeMessageBox(str, "", CClientUIInterface::MSG_ERROR);

it just continues IBD on my side after the warning, it doesn't seem to stop after the error

Could you please check again? InitError() returns false, which should stop execution.

fRet = AppInitInterfaces(node) && AppInitMain(node);

Also, the feature_bind_extra test would fail if the node didn’t actually stop.

@l0rinc l0rinc Aug 27, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems to stop for empty blockchain, but it will just continue if something already exists

# build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -i2psam=invalid_addr
...
025-08-27T23:49:17Z Discover: 2a01:4f9:2a:a61::2
2025-08-27T23:49:17Z [error] Invalid -i2psam address or hostname: 'invalid_addr'
Error: Invalid -i2psam address or hostname: 'invalid_addr'
2025-08-27T23:49:17Z tor: Thread interrupt
2025-08-27T23:49:17Z Shutdown in progress...
2025-08-27T23:49:17Z torcontrol thread exit
2025-08-27T23:49:17Z Loaded 0 blocks from external file in 165ms
2025-08-27T23:49:17Z Reindexing block file blk00001.dat...
...

vs

# build/bin/bitcoind -i2psam=invalid_addr                                                                                                                                                                                               
2025-08-27T23:48:51Z Discover: 2a01:4f9:2a:a61::2                                                                                                                                
2025-08-27T23:48:51Z [error] Invalid -i2psam address or hostname: 'invalid_addr'                                                                                                 
Error: Invalid -i2psam address or hostname: 'invalid_addr'                                                                                                                       
2025-08-27T23:48:51Z tor: Thread interrupt                                                                                                                                       
2025-08-27T23:48:51Z Shutdown in progress...                                                                                                                                     
2025-08-27T23:48:51Z torcontrol thread exit                                                                                                                                      
2025-08-27T23:48:57Z mapport thread exit                                                                                                                                     
2025-08-27T23:48:57Z scheduler thread exit                                                                                                                                       
2025-08-27T23:48:57Z Writing 0 mempool transactions to file...                                                                                                                   
2025-08-27T23:48:57Z Writing 0 unbroadcast transactions to file.                                                                                                                 
2025-08-27T23:48:57Z Dumped mempool: 0.000s to copy, 0.051s to dump, 27 bytes dumped to file                                                                                     
2025-08-27T23:48:57Z Flushed fee estimates to fee_estimates.dat.                                                                                                                 
2025-08-27T23:48:57Z Shutdown done                       

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was not yet able to reproduce the continuation. But just to clarify, @l0rinc : are we talking about a late-fail with “afterglow” or does the node truly keep running?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started it and accidentally left it in the background and it fetched 100k blocks - if it's an "afterglow", it's a resilient one. And I did reproduce it on 3 separate servers, seems like a real bug - though likely not introduced in this PR, will open an issue for it or investigate it myself later this week.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious to see what you find. I’ll try to follow if you open an issue/PR on it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #33276, please resolve this comment

@naiyoma naiyoma left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

Tested with these 3 options:

  • ./build/bin/bitcoind -bind=0.0.0.0=onion -bind=0.0.0.0=onion
  • ./build/bin/bitcoind -bind=0.0.0.0 -bind=0.0.0.0
  • ./build/bin/bitcoind -whitebind=relay@127.0.0.1:8333 -whitebind=mempool@127.0.0.1:8333
bitcoind shuts down after the error [error] Duplicate binding configuration for address 0.0.0.0:8334. Please check your -bind, -whitebind, and onion binding settings. Error: Duplicate binding configuration for address 0.0.0.0:8334. Please check your -bind, -whitebind, and onion binding settings. 2025-08-28T13:58:57Z torcontrol thread start 2025-08-28T13:58:57Z tor: Thread interrupt 2025-08-28T13:58:57Z Shutdown in progress...

For duplicate onion binds, there's an existing warning that appears before the error:
2025-08-28T13:58:57Z [warning] More than one onion bind address is provided.
IMO this warning is sufficient, but if you keep the error approach, consider removing the warning.

@w0xlt

w0xlt commented Aug 28, 2025

Copy link
Copy Markdown
Contributor Author

IMO this warning is sufficient, but if you keep the error approach, consider removing the warning.

Thanks for the review @naiyoma .
The error will occur regardless, whether in master or in this PR.
This PR simply provides a clearer and more useful message.

Regarding removing the warning, I think the bind validation could be moved to occur before the onion validation. Good catch.

Comment thread src/init.cpp Outdated
}

/**
* @brief Checks for duplicate bindings across all binding configuration.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

configuration -> configurations [“across all binding configuration” should be pluralized to “configurations” for grammatical correctness]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1c40b32.

@vasild vasild left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 28fb7f7

Some non-blocker comments below, would be happy to re-review if addressed.

Comment thread src/init.cpp Outdated
/**
* @brief Checks for duplicate bindings across all binding configuration.
*
* @param connOptions Connection options containing the binding vectors to check

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param connOptions Connection options containing the binding vectors to check
* @param[in] connOptions Connection options containing the binding vectors to check

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1c40b32.

Comment thread src/init.cpp
Comment on lines +1236 to +1241
/**
* @brief Checks for duplicate bindings across all binding configuration.
*
* @param connOptions Connection options containing the binding vectors to check
* @return std::optional<CService> containing the first duplicate found, or std::nullopt if no duplicates
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't clear to me what the return value is before reading the comment, so this is useful. Sometimes comments are more on the side of redundant/noisy/useless, but the line between that and useful comments is blurred and personally I prefer to err on the side of redundancy.

If there are no comments whatsoever, this sets a standard. Then if the function is extended with not-so-obvious parameters or return value changed, the developer doing it will likely not add a comment for the new parameter.

Yes, comments need to be maintained to be in sync with the code. But note that the argument "Lets omit some comments because we will have to maintain them (more future work)" is missing a point - a code is read many more times than it is modified. So it is better have code that is "easier to read and 'harder' to modify" than "harder to read and easier to modify".

Comment thread src/init.cpp Outdated
* @param connOptions Connection options containing the binding vectors to check
* @return std::optional<CService> containing the first duplicate found, or std::nullopt if no duplicates
*/
std::optional<CService> CheckBindingConflicts(const CConnman::Options& connOptions)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the new function is used only in this file:

Suggested change
std::optional<CService> CheckBindingConflicts(const CConnman::Options& connOptions)
static std::optional<CService> CheckBindingConflicts(const CConnman::Options& connOptions)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1c40b32.

Comment thread src/init.cpp Outdated

if (auto conflict = CheckBindingConflicts(connOptions)) {
return InitError(Untranslated(strprintf("Duplicate binding configuration for address %s. "
"Please check your -bind, -whitebind, and onion binding settings.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and also in the commit message, this lists 3 categories: -bind, -whitebind, onion. There is an -onion config option which is not related to binding. This could be confusing. The intention here is to mean "onion" -> "a subset of -bind if suffixed with =onion". That is, just mentioning -bind also covers -bind=...=onion. So maybe change this to:

Please check your -bind and -whitebind settings or to
Please check your -bind, -bind=...=onion and -whitebind settings.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1c40b32. Thanks for the review.

Comment thread src/init.cpp Outdated
connOptions.m_i2p_accept_incoming = args.GetBoolArg("-i2pacceptincoming", DEFAULT_I2P_ACCEPT_INCOMING);

if (auto conflict = CheckBindingConflicts(connOptions)) {
return InitError(Untranslated(strprintf("Duplicate binding configuration for address %s. "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we already know which container the duplicate came from

That is known inside CheckBindingConflicts() but the info is not signaled to the caller of the function. Also, even inside the function, only one of the containers is known, the other is not. E.g. if the 3rd for-loop finds a duplicate, then it is unknown whether the entry was added in the first or the second for-loop.

I think this should at least be translatable for consistency with nearby init errors

Right, +1 for translating it.

@DrahtBot DrahtBot requested a review from cedwies September 2, 2025 09:05
Comment thread test/functional/feature_bind_extra.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happened here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuvicc, this still needs fixing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in 4d4789d. Thanks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuvicc, this still needs fixing

Ahh, I thought that was non-blocking.

@yuvicc yuvicc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 1c40b32597712059d5d809925da0e9adccac0fb3

bitcoind stops with error message for duplicate bind options.

Error: Duplicate binding configuration for address 0.0.0.0:8333. Please check your -bind, -bind=...=onion and -whitebind settings.
2025-09-05T05:15:05Z torcontrol thread start
2025-09-05T05:15:05Z Loading 0 mempool transactions from file...
2025-09-05T05:15:05Z Imported mempool transactions from file: 0 succeeded, 0 failed, 0 expired, 0 already there, 0 waiting for initial broadcast
2025-09-05T05:15:05Z initload thread exit

Comment thread src/init.cpp Outdated
* @param connOptions Connection options containing the binding vectors to check
* @return std::optional<CService> containing the first duplicate found, or std::nullopt if no duplicates
*/
std::optional<CService> CheckBindingConflicts(const CConnman::Options& connOptions)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::optional<CService> CheckBindingConflicts(const CConnman::Options& connOptions)
std::optional<CService> CheckBindingConflicts(const CConnman::Options& conn_options)

nit: as per the guidelines, function arguments should be snake_case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 4d4789d. Thanks.

@DrahtBot DrahtBot requested review from l0rinc and vasild September 5, 2025 05:20
Currently, if the user inadvertently starts the node with duplicate bind options,
such as `-bind=0.0.0.0 -bind=0.0.0.0`, it will cause a fatal error with the
misleading message "Bitcoin Core is probably already running".

This commit adds early validation to detect duplicate bindings across all binding
configurations (-bind, -whitebind, and onion bindings) before attempting to bind.
When duplicates are detected, the node terminates with a clear, specific error
message: "Duplicate binding configuration for address <addr>. Please check your
-bind, -bind=...=onion and -whitebind settings."

The validation catches duplicates both within the same option type (e.g.,
`-bind=X -bind=X`) and across different types (e.g., `-bind=X -whitebind=Y@X`),
helping users identify and fix configuration mistakes.
@l0rinc

l0rinc commented Sep 5, 2025

Copy link
Copy Markdown
Contributor

ACK 4d4789d

@DrahtBot DrahtBot requested a review from yuvicc September 5, 2025 06:44
@yuvicc

yuvicc commented Sep 5, 2025

Copy link
Copy Markdown
Contributor

re-ACK 4d4789d

@vasild vasild left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 4d4789d

@naiyoma naiyoma left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested ACK 4d4789d

Manually tested with different duplicate bindings.

./build/bin/bitcoind -bind=0.0.0.0=onion -bind=0.0.0.0=onion
./build/bin/bitcoind -bind=0.0.0.0 -bind=0.0.0.0
./build/bin/bitcoind -whitebind=relay@127.0.0.1:8333 -whitebind=mempool@127.0.0.1:8333

On master, the error message is misleading

2025-09-09T10:59:38Z [net:error] Unable to bind to 0.0.0.0:8334 on this computer. Bitcoin Core is probably already running.
2025-09-09T10:59:38Z [error] Unable to bind to 0.0.0.0:8334 on this computer. Bitcoin Core is probably already running.
Error: Unable to bind to 0.0.0.0:8334 on this computer. Bitcoin Core is probably already running.

After this PR change, the error message is much clearer and points to the issue.

2025-09-09T11:02:19Z [error] Duplicate binding configuration for address 0.0.0.0:8334. Please check your -bind, -bind=...=onion and -whitebind settings.
Error: Duplicate binding configuration for address 0.0.0.0:8334. Please check your -bind, -bind=...=onion and -whitebind settings.

@sipa

sipa commented Sep 9, 2025

Copy link
Copy Markdown
Member

utACK 4d4789d

@achow101

achow101 commented Sep 9, 2025

Copy link
Copy Markdown
Member

ACK 4d4789d

@achow101 achow101 merged commit 2c8a478 into bitcoin:master Sep 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.