Skip to content

net: open $DOG Mode connections after feelers and network-specific peers - #10

Open
dogofbitcoin wants to merge 1 commit into
bitcoindogmode:31.1-dogmodefrom
dogofbitcoin:net-dog-after-feelers
Open

dogofbitcoin wants to merge 1 commit into
bitcoindogmode:31.1-dogmodefrom
dogofbitcoin:net-dog-after-feelers

Conversation

@dogofbitcoin

Copy link
Copy Markdown

In ThreadOpenConnections the dog choice sits above the feeler and network-specific choices in the if / else if chain and has no timer of its own. So while any of the four dog slots is empty, every pass that reaches it becomes a dog attempt, and no feeler or extra network-specific connection is made. While few reachable peers advertise NODE_DOG_MODE, the slots stay empty, so on today's network that is the steady state.

This moves the dog choice below the network-specific one. Every choice Bitcoin Core makes keeps its place and its timer, and the dog slots take every pass those leave free, which is still nearly all of them: a feeler is due about every 2 minutes, a network-specific connection at most about every 5.

Measured on mainnet, on 7503240091 (the merged tip), our node with its 4 dog slots empty and net debug on for 15 min 42 s (2026-09-21, 22:18 to 22:33 UTC): 0 feeler connections, where the 2 minute feeler timer averages about 8 in that window (the chance of none is about 1 in 2,500); 3 extra block-relay attempts on their own timer (that choice sits above dog, so it still runs); 2 dog attempts, both to the same address last seen 451 hours earlier, both timing out.

Test. p2p_dogmode_feelers.py fills the full-relay and block-relay slots, puts IPv4 and onion addresses without NODE_DOG_MODE in addrman, moves the clock past every connection timer, and requires a feeler and a network-specific connection. Then it teaches the node one address advertising NODE_DOG_MODE and requires a dog connection made automatically. Every outbound connection goes through the test framework's SOCKS5 proxy, so nothing reaches the network. On 7503240091 it fails at the first requirement (3 of 3 runs: no feeler, no network-specific connection); with this change it passes (13 of 13 runs).

Also run with this change, all passing: p2p_dogmode_peering.py (both transports), mempool_dust.py, mempool_accept.py, p2p_node_network_limited.py, p2p_add_connections.py, p2p_handshake.py, feature_anchors.py, p2p_seednode.py, p2p_dns_seeds.py, p2p_private_broadcast.py, p2p_eviction.py, p2p_outbound_eviction.py, p2p_addrfetch.py, feature_proxy.py, p2p_i2p_ports.py, p2p_disconnect_ban.py, rpc_net.py, p2p_addr_relay.py, p2p_addrv2_relay.py, feature_addrman.py, and the net_tests, net_peer_connection_tests and addrman_tests unit tests.

A timer of its own for the dog choice would work too; this is the smaller change. It is independent of #7, and found while reading the connection loop for #3.

ThreadOpenConnections chooses one connection type per pass. The $DOG Mode
choice sat above the feeler and network-specific choices and has no timer
of its own, so while any of the MAX_DOG_MODE_CONNECTIONS slots is empty,
every pass that reaches it is taken by it, and no feeler or extra
network-specific connection is ever made. While few peers advertise
NODE_DOG_MODE, the slots can stay empty for hours.

Move the choice below the network-specific one, so every choice Bitcoin
Core makes keeps its order and its timer, and the $DOG Mode slots take
every pass those leave free.

p2p_dogmode_feelers.py fills the full-relay and block-relay slots, moves
the clock past every timer with no address advertising NODE_DOG_MODE
known, and requires a feeler and a network-specific connection; then a
"dog" connection made automatically once such an address is learned.
Without the change it fails at the first requirement.
@dogofbitcoin

Copy link
Copy Markdown
Author

A mainnet reading, 2026-09-22: six new nodes, an hour each from an empty datadir (pruned, no port open, a home connection), three on #7 merged and three with this change on top.

feelers network-specific connections
without this change 0, 1, 0 0, 0, 0
with this change 28, 31, 32 12, 6, 11

Core's feeler timer averages about 30 an hour. The one feeler without the change came at minute 3.3 of its hour, seconds after a sample showed three dog dials sitting in their handshake: a pass reaches the feeler only while all four dog slots are held, which is what the chain in ThreadOpenConnections says and what that minute looked like. Whether a new node met a DOG Mode peer in its first hour did not depend on the change (#7 has that reading).

@tialkan

tialkan commented Oct 10, 2026

Copy link
Copy Markdown

An independent regtest reproduction of the feeler starvation, on the merge commit (7503240091) and on this PR's head, for what it is worth next to the mainnet reading above.

Setup: 24 peer nodes on one host reached through a SOCKS5 mapper, one observer, addrman seeded with the peers' addresses in the new table, 420 s. Three of the 24 peers advertise NODE_DOG_MODE.

build dog connections feelers opened
merged 1 0
this PR 0 4

With a dog slot empty the merged build opened no feeler in 420 s, where about 3.5 are expected at the default interval. With this PR it opened four. That matches the ordering in ThreadOpenConnections.

Two things this does not show. It is one run per build, so the dog count difference between the rows (1 vs 0) is down to which peers the full-relay slots happened to take and says nothing about this PR. And this PR does not change the case where a node has fewer than about ten reachable peers: with 6 of 6 peers signalling, both builds fill 0 of 4 dog slots, because the dog choice is still below the full-relay choice. That is not a criticism of the PR, only a limit on what it fixes.

I did not run the new functional test. Scripts, raw JSON and the build notes are in https://github.com/tialkan/bitcoin-dust-utxo-lab (finding 5, section added 10 October 2026, scripts/exp11_merged_vs_pr10.py).

@krsnak

krsnak commented Oct 27, 2026

Copy link
Copy Markdown

Independent Apple Silicon validation — PR #10 (2026-10-27)

I tested the new p2p_dogmode_feelers.py functional regression on a MacBook Pro M1 (macOS 26.6.2, arm64), comparing the same test against the base commit and this PR.

Revisions and build

  • Base 31.1-dogmode: 75032400914250c7ad857dc29043761680a66485
  • PR head: a0ec4fc7bc1d7310d09a7fc2799fd2b43081859d
  • Apple Clang 21.0.0, Python 3.14.6, CMake + Ninja, Release; wallet, GUI and IPC disabled
  • Native bitcoind built successfully on both revisions
Test Result
Base + the same functional test from PR #10 FAIL (expected): exit 1
PR head + p2p_dogmode_feelers.py PASS, 3 separate runs: exit 0 each
PR head + p2p_dogmode_peering.py --v1transport PASS
PR head + p2p_dogmode_peering.py --v2transport PASS

On the base revision, the scenario reached the intended assertion after filling the 8 full-relay and 2 block-relay slots:

Expected message(s) ['connection (feeler) to', 'Making network specific connection to'] not found in log

On the PR head, the test confirmed that feeler and network-specific attempts still occur with empty DOG slots, and that an outbound DOG connection opens after a NODE_DOG_MODE peer is advertised. All three runs ended with Tests successful. The existing DOG peering tests also passed with both v1 and v2 transport.

Reproduction (after configuring/building bitcoind):

python3 test/functional/p2p_dogmode_feelers.py --configfile=build-pr10/test/config.ini --cachedir=build-pr10/cache-clean --timeout-factor=2
python3 test/functional/p2p_dogmode_peering.py --configfile=build-pr10/test/config.ini --cachedir=build-pr10/cache-clean --v1transport --timeout-factor=2
python3 test/functional/p2p_dogmode_peering.py --configfile=build-pr10/test/config.ini --cachedir=build-pr10/cache-clean --v2transport --timeout-factor=2

Local macOS build caveat: CMake initially detected pipe2 via the installed SDK, although the symbol is only available from macOS 27 and this host runs macOS 26.6.2. The initial test node failed during startup. Reconfiguring this local build with -DHAVE_DECL_PIPE2=0 and rebuilding resolved the startup issue, allowing the functional test to run. This is unrelated to PR #10; I made no tracked source changes (clean worktree).

This is independent local regtest validation, not a mainnet test or full-suite run. Within this scope, the fix resolves the reproduced starvation and I found no regression in the DOG peering checks.

— @krsnak

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants