{
 "number": 35600,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35600",
 "title": "net: prevent duplication manual connections (take 2)",
 "author": "willcl-ark",
 "author_association": "MEMBER",
 "created_at": "2026-06-25T11:37:10Z",
 "updated_at": "2026-08-18T20:24:59Z",
 "age_days": 84,
 "draft": false,
 "labels": [
  "P2P"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "cec131b2487bb8810cea58ac4d0f3983e38e212a",
 "head_ref": "duplicate-connections-2",
 "head_repo": "willcl-ark/bitcoin",
 "head_history": [
  {
   "t": "2026-07-25T11:22:38Z",
   "sha": "cec131b2487bb8810cea58ac4d0f3983e38e212a"
  }
 ],
 "additions": 164,
 "deletions": 5,
 "changed_files": 4,
 "commit_count": 2,
 "size_bucket": "M",
 "mergeable_state": "blocked",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "sedited",
      "url": "https://github.com/bitcoin/bitcoin/pull/35600#issuecomment-5069081203"
     },
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/35600#issuecomment-5333640801"
     }
    ]
   },
   "conflicts": [
    {
     "number": 32278,
     "title": "doc: better document NetEventsInterface and the deletion of \"CNode\"s",
     "author": "vasild"
    }
   ]
  }
 },
 "acks_parsed": {
  "sedited": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-07-24T10:55:27Z",
   "stale": false
  },
  "w0xlt": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-18T20:24:55Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 2,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "sedited",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "blocked",
  "last_author_activity": "2026-07-25T11:22:38Z",
  "last_reviewer_activity": "2026-08-18T20:24:55Z",
  "last_reviewer": "w0xlt",
  "author_silent_days": 54,
  "waiting_on_author_days": 29,
  "days_since_update": 29
 },
 "refs": {
  "mentioned": [
   5299,
   27804,
   28428
  ],
  "depends_on": [],
  "fixes": [
   5299
  ],
  "linked_issues": [
   {
    "number": 5299,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Initial connection management"
   }
  ],
  "references": [
   {
    "number": 5299,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Initial connection management"
   },
   {
    "number": 28428,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2023-09-09",
    "title": "Hard-code version number value for CBlockLocator and CDiskBlockIndex"
   },
   {
    "number": 27804,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "init: deduplicate added connections"
   }
  ],
  "conflicts": [
   32278
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "Fixes #5299.\n\n`-connect` and `-addnode` can both try to open the same manual connection during startup.\nExisting duplicate checks only see peers after they have been inserted into `m_nodes`, so two concurrent manual dial attempts can both pass the checks and create duplicate connections.\n\nFix this by reserving a manual connection destination before connecting in `OpenNetworkConnection()`.\nThe reservation is cleared after a failed connection, or after a successful node has been added to `m_nodes`, where the existing connected-peer checks take over.\n\nThis is intentionally limited to `ConnectionType::MANUAL`, covering `-connect`, `-addnode`, manual reconnections, and one-shot manual RPC connection attempts only.\nNote that this does not try to prevent automatic outbound connections to addnode peers like 21f8426c823fd83c40fd81f08ff2bf39ec6a4575 from #28428 does.\n\nA functional regression test is added for the startup case where two nodes each configure the other with both `-connect` and `-addnode`. This fails before the fixing commit is applied.\n\nIn comparison to #27804 this approach tracks in-flight manual connection attempts in `OpenNetworkConnection()`, instead of de-duplicating configuration arguments, which should be much less-brittle. Once the connection succeeds, the existing `m_nodes` duplicate checks take over.\n\nThe remaining limitation is that hostnames are keyed by the original string, not resolved IPs. So `-connect=example.com` and `-addnode=<resolved-ip>` are not guaranteed to dedupe during the in-flight window, but I don't want to add DNS lookups in here, so I consider this acceptable.",
 "commits": [
  {
   "sha": "624c39e9bc76b09836b4d9992540e6bc34e85018",
   "date": "2026-07-25T10:47:40Z",
   "message": "net: reserve pending manual connections\n\nManual -connect and -addnode callers can run concurrently during\nstartup. Existing duplicate checks only see m_nodes after a successful\nconnection attempt, so both threads can open sockets to the same target.\n\nReserve the normalized manual destination while connecting and release\nit after failure or after publishing the node, so the existing\nconnected-peer checks take over.\n\nThis applies to all `MANUAL` connection attempts, regardless of network,\nbut the keying differs by net type.\n\n- IPv4: by resolved `CService` including port.\n- IPv6: by resolved `CService` including port.\n- CJDNS: by numeric CJDNS IPv6-style addresses, via `MaybeFlipIPv6toCJDNS(...)`.\n- Tor: by v3 onion addresses.\n- I2P: by b32.i2p addresses.\n- Hostnames: keyed by the original destination string, not resolved IPs, because the guard intentionally avoids doing DNS just for the reservation."
  },
  {
   "sha": "cec131b2487bb8810cea58ac4d0f3983e38e212a",
   "date": "2026-07-25T10:47:43Z",
   "message": "test: duplicate manual connections regression test\n\nStarting peers with the same address in both -connect and -addnode can\ncreate duplicate manual connection attempts during startup. Add a\nregression test to prevent this from being re-introduced."
  }
 ],
 "timeline": [
  {
   "t": "2026-07-24T10:55:27Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2026-07-25T11:22:38Z",
   "kind": "force_push",
   "who": "willcl-ark",
   "commit": "cec131b2487bb8810cea58ac4d0f3983e38e212a"
  },
  {
   "t": "2026-08-18T20:24:55Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  }
 ],
 "labels_log": [
  {
   "t": "2026-06-25T11:37:14Z",
   "action": "labeled",
   "label": "P2P",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-24T23:25:15Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-25T12:10:44Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-25T12:23:31Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-30T07:27:00Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "maflcko"
  }
 ],
 "state_log": [],
 "text_chars": 2855,
 "text_tokens_estimate": 713,
 "changed_paths": [
  "src/net.cpp",
  "src/net.h",
  "test/functional/p2p_duplicate_manual_connections.py",
  "test/functional/test_runner.py"
 ],
 "files": [
  {
   "path": "src/net.cpp",
   "add": 52,
   "del": 5
  },
  {
   "path": "src/net.h",
   "add": 6,
   "del": 0
  },
  {
   "path": "test/functional/p2p_duplicate_manual_connections.py",
   "add": 105,
   "del": 0
  },
  {
   "path": "test/functional/test_runner.py",
   "add": 1,
   "del": 0
  }
 ],
 "test_lines": 106,
 "git": {
  "head": "cec131b2487bb8810cea58ac4d0f3983e38e212a",
  "head_matches_backup": true,
  "base": "b33a7fcd7bd896da7175a28802bac9ca53fa238d",
  "commits": [
   {
    "sha": "624c39e9bc",
    "subject": "net: reserve pending manual connections",
    "files": 2,
    "add": 58,
    "del": 5
   },
   {
    "sha": "cec131b248",
    "subject": "test: duplicate manual connections regression test",
    "files": 2,
    "add": 106,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "2d2d3d7778c4c4c8",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}