{
 "number": 35040,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35040",
 "title": "Remove CNode dependency for local address functions and introduce LocalAddressManager",
 "author": "theuni",
 "author_association": "MEMBER",
 "created_at": "2026-04-09T15:53:00Z",
 "updated_at": "2026-08-17T01:34:12Z",
 "age_days": 161,
 "draft": true,
 "labels": [
  "Needs rebase",
  "CI failed"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "dd8968cbd9dc19a4ab91b86080bf9271cdd8b2a2",
 "head_ref": "addrlocal-refactor3",
 "head_repo": "theuni/bitcoin",
 "head_history": [
  {
   "t": "2026-04-09T16:26:00Z",
   "sha": "17431d71e68b7f6a79589e8a84939c9914341015"
  },
  {
   "t": "2026-04-09T16:31:32Z",
   "sha": "070f9c36ca47d5ce9a43264d82abe99a87f9a5b0"
  },
  {
   "t": "2026-04-09T18:59:05Z",
   "sha": "4982f6b7bb11d97f7f1f53e9adff524248deeed4"
  },
  {
   "t": "2026-04-09T20:17:59Z",
   "sha": "61d718aa3bbfdadf8cd78240bfdefd6b7fc8c64b"
  },
  {
   "t": "2026-04-09T21:23:30Z",
   "sha": "0a57e72fb7a9e1c43410b089380a24830c1c3bc0"
  },
  {
   "t": "2026-04-16T16:45:35Z",
   "sha": "dd8968cbd9dc19a4ab91b86080bf9271cdd8b2a2"
  }
 ],
 "additions": 421,
 "deletions": 406,
 "changed_files": 23,
 "commit_count": 23,
 "size_bucket": "L",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "conflicts": [
    {
     "number": 34806,
     "title": "refactor: logging: Various API improvements",
     "author": "ajtowns"
    },
    {
     "number": 34213,
     "title": "net: preserve anchors when network is disabled",
     "author": "brunoerg"
    },
    {
     "number": 34028,
     "title": "p2p: Prevent integer overflow in LocalServiceInfo::nScore",
     "author": "codeabysss"
    },
    {
     "number": 33920,
     "title": "Export embedded ASMap RPC",
     "author": "fjahr"
    },
    {
     "number": 32394,
     "title": "net: make m_nodes_mutex non-recursive",
     "author": "vasild"
    },
    {
     "number": 28690,
     "title": "build: Introduce internal kernel library",
     "author": "sedited"
    }
   ]
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "fjahr",
   "maflcko"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": true,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-04-16T16:45:35Z",
  "last_reviewer_activity": "2026-04-09T20:51:39Z",
  "last_reviewer": "maflcko",
  "author_silent_days": 153,
  "waiting_on_author_days": 0,
  "days_since_update": 31
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": [
   34806,
   34213,
   34028,
   33920,
   32394,
   28690
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "This is part of the [net/net_processing split](https://github.com/bitcoin/bitcoin/issues/33958) project.\n\nThis PR _does not_ change any current behavior, though it does highlight some areas for improvement.\n\n### Background\n\nAll of this trouble is necessary for a single purpose: advertising our own network address to peers. This currently happens in `PeerManagerImpl::MaybeSendAddr`.\n\nIn order to advertise our network address, we need to know _what_ to advertise. We make our best guess based on these criteria (ordered from worst to best):\n\n```c\nenum\n{\n    LOCAL_NONE,   // unknown\n    LOCAL_IF,     // address a local interface listens on\n    LOCAL_BIND,   // address explicit bound to\n    LOCAL_MAPPED, // address reported by PCP\n    LOCAL_MANUAL, // address explicitly specified (-externalip=)\n\n    LOCAL_MAX\n};\n```\n\nBecause we may have more than one publicly visible network address (ipv4, ipv6, tor, etc), we do our best to advertise an address to peers that they would be able to reach. This avoids (for example) disclosing our public ipv4 address to a node connected to our onion service. Accordingly, in order to determine the best address for a given peer which maybe have connected through a proxy, in addition to their address, the LocalAddressManager must also know the network through which they are connected.\n\nWe have one last source of data for what our public network address may be: each peer (as part of the version handshake) tells us what they think our address is. This is untrustworthy data, so it is not added to our list of possible addresses for consideration for other peers. We currently only consider advertising it back to a peer (in `GetLocalAddrForPeer`) if it is routable AND either we don't have a good guess OR randomly. The `LocalAddressManager` introduced in this PR is unrelated to that logic.\n\n### What's happening in this PR (and how to review)\n\n- Because local address management is unrelated to `CConnman`, its reliance on them (via `CNode`) is removed.\n- `LocalAddressManager` is created in order to house the local network address functionality.\n- All functionality is still global in scope, though `LocalAddressManager` is now instantiated when possible for testing and fuzzing\n- There should be no behavioral changes\n\nReviewers should be able to verify that there are no behavioral changes, and that the handling of local address management is now contained and more straightforward.\n\n### Next steps\n\nI stopped at this point because the changes, while straightforward and boring, have already added up. The main obvious change that remains is to remove the `CNode` dependency from `GetLocalAddrForPeer`.\n\nBeyond that:\n- Store addrLocal in `Peer` rather than `CNode` as it is an artifact of the message layer, not the networking layer\n- Move the `GetLocalAddrForPeer` logic to `PeerMan` for the same reason.\n- Instantiate `LocalAddressManager` with options to avoid its use of globals where possible (`fListen`, `fDiscover`, `fLogIPs`).\n- Potentially remove `g_localaddressman` and store an instance in `NodeContext` instead.\n- From a quick glance, I think that fuzzing is currently broken because `g_reachable_nets` is not populated.\n\n### Potential behavioral improvements\n\n- The (unchanged) logic in `LocalAddressManager::Get` uses a peer's IP and the network through which they're connected to decide which of our addresses to advertise. This address doesn't have much meaning when the peer is connected through an outbound proxy or is inbound via tor.\n- Only advertise an address to a peer of the same network through which they're connected to us. OR codify and add tests for what the exceptions should be. The current logic in `LocalAddressManager::Get` and `GetLocalAddrForPeer` is brittle and difficult to reason about.",
 "commits": [
  {
   "sha": "2be3875db7fbeacdf4343c430de67abc753d601c",
   "date": "2026-04-09T16:05:53Z",
   "message": "net: rename variable to simplify review\n\nOtherwise all of the addr variables would start to blend together in subsequent\ncommits."
  },
  {
   "sha": "3ffd518cc60ea69f103025d28f726b3d146e1baa",
   "date": "2026-04-09T16:30:37Z",
   "message": "net: Create IsPrivacyNetwork helper\n\nThis will allow networks to be probed directly rather than requiring a valid\nCNetAddr."
  },
  {
   "sha": "78c70f5c97452f4ce6d66293fd0a906d3135b9d9",
   "date": "2026-04-09T16:30:37Z",
   "message": "net: Don't require CNode for GetLocal\n\nRather than using ConnectedThroughNetwork() and IsPrivacyNetwork() to determine\nwhether this is an incoming Tor node, pass the connected-through network as a\nparameter instead.\n\nThis is in preparation for moving the Local* functions to their own files."
  },
  {
   "sha": "097b69f618f20ab56beafd807c7c0b6d907ea931",
   "date": "2026-04-09T16:30:37Z",
   "message": "net: Add tests for GetLocalAddress for inbound onion peers\n\nAll nodes are changed to inbound for the tests, but it only matters for the\nonions."
  },
  {
   "sha": "a1a4c155db91f0213f069507614161fc9249127e",
   "date": "2026-04-09T16:30:37Z",
   "message": "net: have GetLocalAddress return an optional\n\nRather than an invalid address to indicate failure"
  },
  {
   "sha": "98b291f8cb1fc2424e5c0af269b956af5d6d75d0",
   "date": "2026-04-09T16:30:37Z",
   "message": "net: Don't require CNode for GetLocalAddress\n\nSimilar to the previous change for GetLocal. Outside callers no longer have to\nuse a CNode, so the tests have been simplified."
  },
  {
   "sha": "1f64390a2e45eb849a989b099a1f14cf5f7dd5fa",
   "date": "2026-04-09T16:30:37Z",
   "message": "net: remove unnecessary AddLocal overload"
  },
  {
   "sha": "317f80525fffe1858477f2d6afdce4ba8e6c9b85",
   "date": "2026-04-09T21:23:21Z",
   "message": "net: Move local address functions to a new file"
  },
  {
   "sha": "a446c253bece89d8fe93b590a88bae69b3eadd04",
   "date": "2026-04-09T21:23:21Z",
   "message": "net: move GetnScore out of net"
  },
  {
   "sha": "7b73db35dcfd5a22861f60c190c1cc8a012843d9",
   "date": "2026-04-09T21:23:21Z",
   "message": "net: move getNetLocalAddresses out of net"
  },
  {
   "sha": "1003a888789d7bb996812c11bd973092dec32d23",
   "date": "2026-04-09T21:23:21Z",
   "message": "tests: Remove old test that doesn't test anything useful\n\nThis test specificaly demonstrated the fix for a previously-undefined variable.\nIt is not helpful for detecting new issues."
  },
  {
   "sha": "c1aeea1c04280e8fc75d00286577497b8317fe25",
   "date": "2026-04-09T21:23:21Z",
   "message": "rpc: use local addresses from connman rather than accessing a global"
  },
  {
   "sha": "00735ef1e6376bc52c7ee02c7c0f2cfe84019515",
   "date": "2026-04-09T21:23:21Z",
   "message": "fuzz: avoid accessing mapLocalHost directly"
  },
  {
   "sha": "d57ace41f0f9b0a17f564287ef0bb60f814c44c7",
   "date": "2026-04-09T21:23:21Z",
   "message": "net: move mapLocalHost globals out of net and make them static"
  },
  {
   "sha": "e8d01006970d9269c81262d2336faecffa5f41f6",
   "date": "2026-04-09T21:23:21Z",
   "message": "net: Create LocalAddressManager\n\nMove all local address related functions to a class and create a global\ninstance of it. Make the global a unique_ptr so that in the future we can\nconstruct it with runtime-defined options at startup.\n\nBecause usage in bitcoind is still global, this is mostly a no-op. But it\nallows for using non-global instances in tests.\n\nNote that the global class still access some other globals:\nfListen\nfDiscover\nfLogIPs\ng_reachable_nets\n\nThese will be passed in to individual LocalAddressManager instances in the\nfuture."
  },
  {
   "sha": "282444f149cb4cc9620c3a6fe072e6d72dd15475",
   "date": "2026-04-09T21:23:21Z",
   "message": "net: move mapLocalHost and its mutex to LocalAddressManager"
  },
  {
   "sha": "f78c7ad341c42bf50f0eaab4414e4cf1980c0964",
   "date": "2026-04-09T21:23:21Z",
   "message": "net: Clean up log messages in LocalAddressManager"
  },
  {
   "sha": "bd29bc3990eb929b394b34104944c96840dc492d",
   "date": "2026-04-09T21:23:21Z",
   "message": "tests: Use instances of LocalAddressManager rather than global functions\n\nBecause LocalAddressManager is scoped, per-instance removal happens\nautomatically."
  },
  {
   "sha": "9310e48b6568ae889464dd4a551aa776a8cd23ad",
   "date": "2026-04-09T21:23:21Z",
   "message": "fuzz: use local instance of LocalAddressManager"
  },
  {
   "sha": "a29a9aeda39eb55f1a549891b2ddacd6f58f5b61",
   "date": "2026-04-09T21:23:21Z",
   "message": "net: Use global LocalAddressManager instance directly\n\nAlso get rid of old forwarding functions.\n\nIn the future, the global instance can be replaced with an instance held\nin NodeContext and passed to the subsystems which require it."
  },
  {
   "sha": "cb8f6b223b5f214ff7f3ebcec61c61720083c50a",
   "date": "2026-04-09T21:23:21Z",
   "message": "net: Only require the necessary network class for LocalAddressManager functions\n\nRequiring CService or CAddress may lead callers to assume that the port or\nservices are used in these functions."
  },
  {
   "sha": "24a168a4bad7fe1e39cf0f8c82409a34020c41db",
   "date": "2026-04-16T16:36:37Z",
   "message": "net: move GetReachabilityFrom to LocalAddressManager\n\nIt is only used for determining which addresses can be advertised to a peer."
  },
  {
   "sha": "dd8968cbd9dc19a4ab91b86080bf9271cdd8b2a2",
   "date": "2026-04-16T16:44:12Z",
   "message": "net: move globals from net.h to resolve circular dependency\n\nSee comment in netglobals.h. This is a simple and temporary work-around for the\ncircular dependency introduced by LocalAddressManager. These globals will be\neliminated in a follow-up PR."
  }
 ],
 "timeline": [
  {
   "t": "2026-04-09T16:26:00Z",
   "kind": "force_push",
   "who": "theuni",
   "commit": "17431d71e68b7f6a79589e8a84939c9914341015"
  },
  {
   "t": "2026-04-09T16:31:32Z",
   "kind": "force_push",
   "who": "theuni",
   "commit": "070f9c36ca47d5ce9a43264d82abe99a87f9a5b0"
  },
  {
   "t": "2026-04-09T18:35:41Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "CONTRIBUTOR",
   "text": "Typo in title: \"LocalAddressManger\" :)"
  },
  {
   "t": "2026-04-09T18:59:05Z",
   "kind": "force_push",
   "who": "theuni",
   "commit": "4982f6b7bb11d97f7f1f53e9adff524248deeed4"
  },
  {
   "t": "2026-04-09T18:59:23Z",
   "kind": "comment",
   "who": "theuni",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nClearly this was supposed to be LocalAddressMonger, peddler of the finest Local Addresses..."
  },
  {
   "t": "2026-04-09T19:01:37Z",
   "kind": "comment",
   "who": "theuni",
   "assoc": "MEMBER",
   "text": "@maflcko I suspect you'll want to weigh in here on namespaces and/or file locations?"
  },
  {
   "t": "2026-04-09T20:17:59Z",
   "kind": "force_push",
   "who": "theuni",
   "commit": "61d718aa3bbfdadf8cd78240bfdefd6b7fc8c64b"
  },
  {
   "t": "2026-04-09T20:51:39Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI think this one is easy: You are adding a new file to `bitcoin_node` lib, so it should be in the node folder, like the other p2p stuff, like `node/eviction.cpp`, or `node/txorphanage.cpp`."
  },
  {
   "t": "2026-04-09T21:23:30Z",
   "kind": "force_push",
   "who": "theuni",
   "commit": "0a57e72fb7a9e1c43410b089380a24830c1c3bc0"
  },
  {
   "t": "2026-04-10T19:20:07Z",
   "kind": "comment",
   "who": "theuni",
   "assoc": "MEMBER",
   "text": "The fix for the circular dependency problem here is non-trivial. Converting to draft while I investigate."
  },
  {
   "t": "2026-04-16T16:45:35Z",
   "kind": "force_push",
   "who": "theuni",
   "commit": "dd8968cbd9dc19a4ab91b86080bf9271cdd8b2a2"
  }
 ],
 "labels_log": [
  {
   "t": "2026-04-09T16:06:33Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-09T16:32:19Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-09T18:14:15Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-24T23:28:56Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-04-09T18:59:40Z",
   "kind": "renamed",
   "who": "theuni",
   "from": "Remove CNode dependency for local address functions and introduce LocalAddressManger",
   "to": "Remove CNode dependency for local address functions and introduce LocalAddressManager"
  },
  {
   "t": "2026-04-10T19:20:58Z",
   "kind": "convert_to_draft",
   "who": "theuni"
  }
 ],
 "text_chars": 7571,
 "text_tokens_estimate": 1892,
 "changed_paths": [
  "src/CMakeLists.txt",
  "src/init.cpp",
  "src/interfaces/node.h",
  "src/local_addresses.cpp",
  "src/local_addresses.h",
  "src/mapport.cpp",
  "src/net.cpp",
  "src/net.h",
  "src/net_processing.cpp",
  "src/netaddress.cpp",
  "src/netaddress.h",
  "src/netglobals.cpp",
  "src/netglobals.h",
  "src/node/interfaces.cpp",
  "src/qt/clientmodel.cpp",
  "src/qt/rpcconsole.cpp",
  "src/rpc/net.cpp",
  "src/test/fuzz/net.cpp",
  "src/test/fuzz/netaddress.cpp",
  "src/test/fuzz/util/net.h",
  "src/test/net_tests.cpp",
  "src/test/util/setup_common.cpp",
  "src/torcontrol.cpp"
 ],
 "files": [
  {
   "path": "src/CMakeLists.txt",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/init.cpp",
   "add": 3,
   "del": 1
  },
  {
   "path": "src/interfaces/node.h",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/local_addresses.cpp",
   "add": 191,
   "del": 0
  },
  {
   "path": "src/local_addresses.h",
   "add": 66,
   "del": 0
  },
  {
   "path": "src/mapport.cpp",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/net.cpp",
   "add": 20,
   "del": 144
  },
  {
   "path": "src/net.h",
   "add": 0,
   "del": 34
  },
  {
   "path": "src/net_processing.cpp",
   "add": 3,
   "del": 1
  },
  {
   "path": "src/netaddress.cpp",
   "add": 0,
   "del": 78
  },
  {
   "path": "src/netaddress.h",
   "add": 10,
   "del": 4
  },
  {
   "path": "src/netglobals.cpp",
   "add": 13,
   "del": 0
  },
  {
   "path": "src/netglobals.h",
   "add": 22,
   "del": 0
  },
  {
   "path": "src/node/interfaces.cpp",
   "add": 2,
   "del": 4
  },
  {
   "path": "src/qt/clientmodel.cpp",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/qt/rpcconsole.cpp",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/rpc/net.cpp",
   "add": 4,
   "del": 2
  },
  {
   "path": "src/test/fuzz/net.cpp",
   "add": 14,
   "del": 12
  },
  {
   "path": "src/test/fuzz/netaddress.cpp",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/test/fuzz/util/net.h",
   "add": 5,
   "del": 0
  },
  {
   "path": "src/test/net_tests.cpp",
   "add": 51,
   "del": 120
  },
  {
   "path": "src/test/util/setup_common.cpp",
   "add": 3,
   "del": 1
  },
  {
   "path": "src/torcontrol.cpp",
   "add": 4,
   "del": 3
  }
 ],
 "test_lines": 209,
 "git": {
  "head": "dd8968cbd9dc19a4ab91b86080bf9271cdd8b2a2",
  "head_matches_backup": true,
  "base": "141fbe4d530b51345e62dee1348e82d8a0406ffc",
  "commits": [
   {
    "sha": "2be3875db7",
    "subject": "net: rename variable to simplify review",
    "files": 1,
    "add": 3,
    "del": 3
   },
   {
    "sha": "3ffd518cc6",
    "subject": "net: Create IsPrivacyNetwork helper",
    "files": 1,
    "add": 10,
    "del": 3
   },
   {
    "sha": "78c70f5c97",
    "subject": "net: Don't require CNode for GetLocal",
    "files": 1,
    "add": 5,
    "del": 5
   },
   {
    "sha": "097b69f618",
    "subject": "net: Add tests for GetLocalAddress for inbound onion peers",
    "files": 1,
    "add": 7,
    "del": 3
   },
   {
    "sha": "a1a4c155db",
    "subject": "net: have GetLocalAddress return an optional",
    "files": 3,
    "add": 17,
    "del": 16
   },
   {
    "sha": "98b291f8cb",
    "subject": "net: Don't require CNode for GetLocalAddress",
    "files": 5,
    "add": 37,
    "del": 54
   },
   {
    "sha": "1f64390a2e",
    "subject": "net: remove unnecessary AddLocal overload",
    "files": 2,
    "add": 1,
    "del": 7
   },
   {
    "sha": "317f80525f",
    "subject": "net: Move local address functions to a new file",
    "files": 18,
    "add": 159,
    "del": 119
   },
   {
    "sha": "a446c253be",
    "subject": "net: move GetnScore out of net",
    "files": 3,
    "add": 8,
    "del": 7
   },
   {
    "sha": "7b73db35dc",
    "subject": "net: move getNetLocalAddresses out of net",
    "files": 5,
    "add": 8,
    "del": 12
   },
   {
    "sha": "1003a88878",
    "subject": "tests: Remove old test that doesn't test anything useful",
    "files": 1,
    "add": 0,
    "del": 53
   },
   {
    "sha": "c1aeea1c04",
    "subject": "rpc: use local addresses from connman rather than accessing a global",
    "files": 1,
    "add": 2,
    "del": 2
   },
   {
    "sha": "00735ef1e6",
    "subject": "fuzz: avoid accessing mapLocalHost directly",
    "files": 1,
    "add": 1,
    "del": 2
   },
   {
    "sha": "d57ace41f0",
    "subject": "net: move mapLocalHost globals out of net and make them static",
    "files": 3,
    "add": 3,
    "del": 5
   },
   {
    "sha": "e8d0100697",
    "subject": "net: Create LocalAddressManager",
    "files": 2,
    "add": 54,
    "del": 20
   },
   {
    "sha": "282444f149",
    "subject": "net: move mapLocalHost and its mutex to LocalAddressManager",
    "files": 2,
    "add": 31,
    "del": 29
   },
   {
    "sha": "f78c7ad341",
    "subject": "net: Clean up log messages in LocalAddressManager",
    "files": 1,
    "add": 2,
    "del": 2
   },
   {
    "sha": "bd29bc3990",
    "subject": "tests: Use instances of LocalAddressManager rather than global functions",
    "files": 1,
    "add": 50,
    "del": 50
   },
   {
    "sha": "9310e48b65",
    "subject": "fuzz: use local instance of LocalAddressManager",
    "files": 1,
    "add": 9,
    "del": 11
   },
   {
    "sha": "a29a9aeda3",
    "subject": "net: Use global LocalAddressManager instance directly",
    "files": 9,
    "add": 22,
    "del": 41
   },
   {
    "sha": "cb8f6b223b",
    "subject": "net: Only require the necessary network class for LocalAddressManager functions",
    "files": 2,
    "add": 11,
    "del": 13
   },
   {
    "sha": "24a168a4ba",
    "subject": "net: move GetReachabilityFrom to LocalAddressManager",
    "files": 5,
    "add": 82,
    "del": 81
   },
   {
    "sha": "dd8968cbd9",
    "subject": "net: move globals from net.h to resolve circular dependency",
    "files": 11,
    "add": 44,
    "del": 13
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "0c8269a34d82ecf3",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}