{
 "number": 30988,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/30988",
 "title": "Split CConnman",
 "author": "vasild",
 "author_association": "CONTRIBUTOR",
 "created_at": "2024-09-27T07:57:10Z",
 "updated_at": "2026-08-02T01:48:21Z",
 "age_days": 720,
 "draft": true,
 "labels": [
  "Needs rebase"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "6091e947504a13df9ee3310e0fce4585c79d19db",
 "head_ref": "sockman",
 "head_repo": "vasild/bitcoin",
 "head_history": [
  {
   "t": "2024-09-27T15:05:31Z",
   "sha": "70c2f13f83a5cc740330d0b4af9cbd74515be6b2"
  },
  {
   "t": "2024-11-14T16:55:15Z",
   "sha": "c4f51c7f61e6c4e291025e450f6dd97c2b939b56"
  },
  {
   "t": "2024-11-19T13:15:12Z",
   "sha": "2b0103705fefddff6b880b9eb72fb590740a7300"
  },
  {
   "t": "2024-12-03T13:59:28Z",
   "sha": "645f625e29b36321bcba8db5758b0428b8ebd80f"
  },
  {
   "t": "2024-12-19T05:48:11Z",
   "sha": "b8b042626ea65c2c2b355a6de69cb7c98bd04de3"
  },
  {
   "t": "2025-01-13T14:31:15Z",
   "sha": "f71f1a346c71909750e5e7d11305dd0a1c24373c"
  },
  {
   "t": "2025-01-14T16:39:29Z",
   "sha": "d83ff66fd74654e67b56d11a7d8e92d38a4bd17a"
  },
  {
   "t": "2025-01-14T17:37:26Z",
   "sha": "bcf1254e91782a33b5a1db542f3758831e0bfe2e"
  },
  {
   "t": "2025-01-28T17:18:56Z",
   "sha": "266ac3267390270e6794d478aa1fbe40e88ffb0f"
  },
  {
   "t": "2025-01-30T14:01:58Z",
   "sha": "7866c736c87a908cef75dc6901c9c0594c65b0eb"
  },
  {
   "t": "2025-02-07T19:25:32Z",
   "sha": "7d84f431f9fd89b647f385ea5224b5ad76236844"
  },
  {
   "t": "2025-02-10T11:43:17Z",
   "sha": "7b63b4ca1c4bc82bb1673313b326888cdb7eda9b"
  },
  {
   "t": "2025-02-13T10:41:41Z",
   "sha": "c4ab7f82d6a4265fb96159b6c973ad849df4f8b9"
  },
  {
   "t": "2025-02-14T08:37:14Z",
   "sha": "563afdd975771cd2b87c4a96e0059f1c6f8774ad"
  },
  {
   "t": "2025-02-14T12:17:47Z",
   "sha": "e1671ff42c385f611af778380a83e98920cee82e"
  },
  {
   "t": "2025-02-17T08:52:04Z",
   "sha": "741f17e51ddac002fadbc735e2ca650117594a3c"
  },
  {
   "t": "2025-03-20T14:23:22Z",
   "sha": "f2f9ff9823fa9ef84ab74af675dad36a390a6b34"
  },
  {
   "t": "2025-04-09T08:12:55Z",
   "sha": "7ef9661fb09d2cd9356cca7476256fea2077bec9"
  },
  {
   "t": "2025-05-02T05:52:36Z",
   "sha": "503791ed57739a5d76aebbb10a39de15dd61eff6"
  },
  {
   "t": "2025-05-21T12:02:33Z",
   "sha": "9cf56ad085d88f0d5fa1b8751ac1ed04e751a5fd"
  },
  {
   "t": "2025-05-21T13:28:40Z",
   "sha": "a8fcd8385e1ca382378ef0450c742ec714f67e99"
  },
  {
   "t": "2025-06-13T12:20:33Z",
   "sha": "6aa43a298d10d1de77f34c392b86302522e50bc4"
  },
  {
   "t": "2025-10-06T11:20:08Z",
   "sha": "8725d10df5050f9ac2a7cbedf2502716786fca84"
  },
  {
   "t": "2025-10-06T17:39:35Z",
   "sha": "dd32dfaaf326f9450a8dbc677c9b05588b00b39d"
  },
  {
   "t": "2025-10-07T08:24:58Z",
   "sha": "69c015b258c3bbfb44af478d7a5a05ad0b0156b8"
  },
  {
   "t": "2025-11-04T14:09:14Z",
   "sha": "6091e947504a13df9ee3310e0fce4585c79d19db"
  }
 ],
 "additions": 1430,
 "deletions": 751,
 "changed_files": 20,
 "commit_count": 17,
 "size_bucket": "XL",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "ryanofsky",
      "url": "https://github.com/bitcoin/bitcoin/pull/30988#pullrequestreview-3472979975"
     }
    ],
    "concept_nack": [
     {
      "login": "theuni",
      "url": "https://github.com/bitcoin/bitcoin/pull/30988#issuecomment-2702063374"
     }
    ],
    "concept_ack": [
     {
      "login": "tdb3",
      "url": "https://github.com/bitcoin/bitcoin/pull/30988#pullrequestreview-2336026243"
     },
     {
      "login": "Sjors",
      "url": "https://github.com/bitcoin/bitcoin/pull/30988#pullrequestreview-2552832660"
     },
     {
      "login": "hodlinator",
      "url": "https://github.com/bitcoin/bitcoin/pull/30988#pullrequestreview-2582270517"
     },
     {
      "login": "jonatack",
      "url": "https://github.com/bitcoin/bitcoin/pull/30988#pullrequestreview-2621788527"
     },
     {
      "login": "rkrux",
      "url": "https://github.com/bitcoin/bitcoin/pull/30988#issuecomment-4380270930"
     }
    ],
    "stale_ack": [
     {
      "login": "pinheadmz",
      "url": "https://github.com/bitcoin/bitcoin/pull/30988#pullrequestreview-2373476515"
     }
    ]
   },
   "conflicts": [
    {
     "number": 33956,
     "title": "net: fix use-after-free with v2->v1 reconnection logic",
     "author": "Crypt-iQ"
    },
    {
     "number": 32394,
     "title": "net: make m_nodes_mutex non-recursive",
     "author": "vasild"
    },
    {
     "number": 32278,
     "title": "doc: better document NetEventsInterface and the deletion of \"CNode\"s",
     "author": "vasild"
    },
    {
     "number": 32015,
     "title": "net: replace manual reference counting of CNode with shared_ptr",
     "author": "vasild"
    },
    {
     "number": 30951,
     "title": "net: option to disallow v1 connection on ipv4 and ipv6 peers",
     "author": "stratospher"
    },
    {
     "number": 30116,
     "title": "p2p: Fill reconciliation sets (Erlay) attempt 2",
     "author": "sr-gi"
    },
    {
     "number": 29641,
     "title": "scripted-diff: Use LogInfo over LogPrintf",
     "author": "maflcko"
    },
    {
     "number": 29418,
     "title": "rpc: provide per message stats for global traffic via new RPC 'getnetmsgstats'",
     "author": "vasild"
    },
    {
     "number": 29415,
     "title": "Broadcast own transactions only via short-lived Tor or I2P connections",
     "author": "vasild"
    },
    {
     "number": 28463,
     "title": "p2p: Increase inbound capacity for block-relay only connections",
     "author": "mzumsande"
    }
   ]
  }
 },
 "acks_parsed": {
  "tdb3": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2024-09-29T22:05:50Z",
   "stale": false
  },
  "pinheadmz": {
   "kind": "ack",
   "hash": "70c2f13f83a5cc740330d0b4af9cbd74515be6b2",
   "t": "2024-10-18T15:14:27Z",
   "stale": true
  },
  "Sjors": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2025-01-15T14:25:01Z",
   "stale": false
  },
  "hodlinator": {
   "kind": "concept_ack",
   "hash": "46131d44faeb797909e0fc3a2042492adef9aa0d",
   "t": "2025-02-01T20:37:50Z",
   "stale": false
  },
  "jonatack": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2025-02-17T18:44:34Z",
   "stale": false
  },
  "ryanofsky": {
   "kind": "ack",
   "hash": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "t": "2025-11-17T19:40:52Z",
   "stale": false
  },
  "rkrux": {
   "kind": "concept_ack",
   "hash": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "t": "2026-05-05T14:32:14Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 1,
  "stale_ack": 1,
  "concept_ack": 5,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 2,
  "changes_requested": 1,
  "distinct_reviewers": [
   "Jacksonearl2468",
   "Sjors",
   "fanquake",
   "hodlinator",
   "jonatack",
   "maflcko",
   "pinheadmz",
   "rkrux",
   "ryanofsky",
   "tdb3",
   "theuni"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": false,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-03-05T10:23:44Z",
  "last_reviewer_activity": "2026-05-05T14:32:14Z",
  "last_reviewer": "rkrux",
  "author_silent_days": 196,
  "waiting_on_author_days": 135,
  "days_since_update": 46
 },
 "refs": {
  "mentioned": [
   25832,
   30043,
   30694,
   30937,
   31011,
   31072,
   31098,
   31223,
   31854,
   32061,
   32747
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 25832,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-02-05",
    "title": "tracing: network connection tracepoints"
   },
   {
    "number": 30043,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2024-09-30",
    "title": "net: Replace libnatpmp with built-in PCP+NATPMP implementation"
   },
   {
    "number": 30694,
    "type": "issue",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "Split socket handling out of CConnman"
   },
   {
    "number": 30937,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2024-10-10",
    "title": "build: scripted-diff: drop config/ subdir for bitcoin-config.h"
   },
   {
    "number": 31011,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2024-10-02",
    "title": "refactor: move util/pcp and util/netif to common/"
   },
   {
    "number": 31072,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2024-12-06",
    "title": "refactor: Clean up messy strformat and bilingual_str usages"
   },
   {
    "number": 31098,
    "type": "issue",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "Stratum v2 via IPC Mining Interface tracking issue"
   },
   {
    "number": 31223,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2024-12-13",
    "title": "net, init: derive default onion port if a user specified a -port"
   },
   {
    "number": 31854,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-02-14",
    "title": "net: reduce CAddress usage to CService or CNetAddr"
   },
   {
    "number": 32061,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "Replace libevent with our own HTTP and socket-handling implementation"
   },
   {
    "number": 32747,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "Introduce SockMan (\\\"lite\\\"): low-level socket handling for HTTP"
   }
  ],
  "conflicts": [
   33956,
   32394,
   32278,
   32015,
   30951,
   30116,
   29641,
   29418,
   29415,
   28463
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/common/sockman.cpp",
  "src/common/sockman.h",
  "src/net.cpp",
  "src/net.h",
  "src/net_processing.cpp",
  "src/rpc/net.cpp",
  "src/test/fuzz/connman.cpp",
  "src/test/fuzz/net.cpp",
  "src/test/net_peer_connection_tests.cpp",
  "src/test/util/net.h"
 ],
 "body": "Currently `CConnman` is a mixture of:\n* low level socket handling, e.g. send, recv, poll, bind, listen, connect, and\n* higher level logic that is specific to the Bitcoin P2P protocol, e.g. V1/V2 transport, choosing which address to connect to, if we manage to connect mark the address good in `AddrMan`, maintaining the number of inbound and outbound connections, banning of peers, interacting with `PeerManager`.\n\n---\n\nThis PR splits the socket handling into a new class which makes the code more modular and reusable. Having more modular and reusable code is a good thing on its own, even if the code is not reused. Stratum V2 and libevent-less RPC/HTTP server could benefit from this, but it makes sense on its own, even without those projects.\n\n---\n\nThe socket operations are driven by the new class `SockMan` which informs the higher level via provided methods when e.g. new data arrives on the socket or a new connection is accepted. For this, `SockMan` provides some non-virtual methods to start it rolling and then it calls pure virtual methods which are implemented by the higher level (e.g. `CConnman`) on certain events, for example \"got this new data on this node's socket\".\n\nThe interface of `SockMan` is:\n\n```cpp\n/**\n * A socket manager class which handles socket operations.\n * To use this class, inherit from it and implement the pure virtual methods.\n * Handled operations:\n * - binding and listening on sockets\n * - starting of necessary threads to process socket operations\n * - accepting incoming connections\n * - making outbound connections\n * - closing connections\n * - waiting for IO readiness on sockets and doing send/recv accordingly\n */\nclass SockMan\n{\npublic:\n\n    //\n    // Non-virtual functions, to be reused by children classes.\n    //\n\n    /**\n     * Bind to a new address:port, start listening and add the listen socket to `m_listen`.\n     * Should be called before `StartSocketsThreads()`.\n     * @param[in] to Where to bind.\n     * @param[out] err_msg Error string if an error occurs.\n     * @retval true Success.\n     * @retval false Failure, `err_msg` will be set.\n     */\n    bool BindAndStartListening(const CService& to, bilingual_str& err_msg);\n\n    /**\n     * Start the necessary threads for sockets IO.\n     */\n    void StartSocketsThreads(const Options& options);\n\n    /**\n     * Join (wait for) the threads started by `StartSocketsThreads()` to exit.\n     */\n    void JoinSocketsThreads();\n\n    /**\n     * Make an outbound connection, save the socket internally and return a newly generated connection id.\n     * @param[in] to The address to connect to, either as CService or a host as string and port as\n     * an integer, if the later is used, then `proxy` must be valid.\n     * @param[in] is_important If true, then log failures with higher severity.\n     * @param[in] proxy Proxy to connect through, if set.\n     * @param[out] proxy_failed If `proxy` is valid and the connection failed because of the\n     * proxy, then it will be set to true.\n     * @param[out] me If the connection was successful then this is set to the address on the\n     * local side of the socket.\n     * @return Newly generated id, or std::nullopt if the operation fails.\n     */\n    std::optional<SockMan::Id> ConnectAndMakeId(const std::variant<CService, StringHostIntPort>& to,\n                                                bool is_important,\n                                                std::optional<Proxy> proxy,\n                                                bool& proxy_failed,\n                                                CService& me)\n        EXCLUSIVE_LOCKS_REQUIRED(!m_connected_mutex, !m_unused_i2p_sessions_mutex);\n\n    /**\n     * Destroy a given connection by closing its socket and release resources occupied by it.\n     * @param[in] id Connection to destroy.\n     * @return Whether the connection existed and its socket was closed by this call.\n     */\n    bool CloseConnection(Id id)\n        EXCLUSIVE_LOCKS_REQUIRED(!m_connected_mutex);\n\n    /**\n     * Try to send some data over the given connection.\n     * @param[in] id Identifier of the connection.\n     * @param[in] data The data to send, it might happen that only a prefix of this is sent.\n     * @param[in] will_send_more Used as an optimization if the caller knows that they will\n     * be sending more data soon after this call.\n     * @param[out] errmsg If <0 is returned then this will contain a human readable message\n     * explaining the error.\n     * @retval >=0 The number of bytes actually sent.\n     * @retval <0 A permanent error has occurred.\n     */\n    ssize_t SendBytes(Id id,\n                      std::span<const unsigned char> data,\n                      bool will_send_more,\n                      std::string& errmsg) const\n        EXCLUSIVE_LOCKS_REQUIRED(!m_connected_mutex);\n\n    /**\n     * Stop listening by closing all listening sockets.\n     */\n    void StopListening();\n\n    //\n    // Pure virtual functions must be implemented by children classes.\n    //\n\n    /**\n     * Be notified when a new connection has been accepted.\n     * @param[in] id Id of the newly accepted connection.\n     * @param[in] me The address and port at our side of the connection.\n     * @param[in] them The address and port at the peer's side of the connection.\n     * @retval true The new connection was accepted at the higher level.\n     * @retval false The connection was refused at the higher level, so the\n     * associated socket and id should be discarded by `SockMan`.\n     */\n    virtual bool EventNewConnectionAccepted(Id id,\n                                            const CService& me,\n                                            const CService& them) = 0;\n\n    /**\n     * Called when the socket is ready to send data and `ShouldTryToSend()` has\n     * returned true. This is where the higher level code serializes its messages\n     * and calls `SockMan::SendBytes()`.\n     * @param[in] id Id of the connection whose socket is ready to send.\n     * @param[out] cancel_recv Should always be set upon return and if it is true,\n     * then the next attempt to receive data from that connection will be omitted.\n     */\n    virtual void EventReadyToSend(Id id, bool& cancel_recv) = 0;\n\n    /**\n     * Called when new data has been received.\n     * @param[in] id Connection for which the data arrived.\n     * @param[in] data Received data.\n     */\n    virtual void EventGotData(Id id, std::span<const uint8_t> data) = 0;\n\n    /**\n     * Called when the remote peer has sent an EOF on the socket. This is a graceful\n     * close of their writing side, we can still send and they will receive, if it\n     * makes sense at the application level.\n     * @param[in] id Connection whose socket got EOF.\n     */\n    virtual void EventGotEOF(Id id) = 0;\n\n    /**\n     * Called when we get an irrecoverable error trying to read from a socket.\n     * @param[in] id Connection whose socket got an error.\n     * @param[in] errmsg Message describing the error.\n     */\n    virtual void EventGotPermanentReadError(Id id, const std::string& errmsg) = 0;\n};\n```\n\nResolves: https://github.com/bitcoin/bitcoin/issues/30694\n\n---\n\nReview hint: this PR moves some code around, so reviewers may find this helpful: `git show --color-moved --color-moved-ws=allow-indentation-change`.",
 "commits": [
  {
   "sha": "1b2a87b3700eb114b8849e05bc488394fa8651bc",
   "date": "2025-11-04T13:56:42Z",
   "message": "net: separate the listening socket from the permissions\n\nThey were coupled in `struct ListenSocket`, but the socket belongs to\nthe lower level transport protocol, whereas the permissions are specific\nto the higher Bitcoin P2P protocol."
  },
  {
   "sha": "24625de4061b5711b43da6265855a185e1f7852d",
   "date": "2025-11-04T13:56:43Z",
   "message": "net: drop CConnman::ListenSocket\n\nNow that `CConnman::ListenSocket` is a `struct` that contains only one\nmember variable of type `std::shared_ptr<Sock>`, drop `ListenSocket` and\nuse `shared_ptr` directly.\n\nReplace the vector of `ListenSocket` with a vector of `shared_ptr`."
  },
  {
   "sha": "0a22cda725a5b42398bfd429d727def06533c747",
   "date": "2025-11-04T13:56:44Z",
   "message": "net: split CConnman::BindListenPort() off CConnman\n\nIntroduce a new low-level socket managing class `SockMan`\nand move the `CConnman::BindListenPort()` method to it."
  },
  {
   "sha": "6ba0aab8658a7bd553a96e441aa1d5e016e086c3",
   "date": "2025-11-04T13:56:44Z",
   "message": "style: modernize the style of SockMan::BindListenPort()\n\nIt was copied verbatim from `CConnman::BindListenPort()` in the previous\ncommit. Modernize its variables and style and log the error messages\nfrom the caller. Also categorize the informative messages to the \"net\"\ncategory because they are quite specific to the networking layer."
  },
  {
   "sha": "3e881168755392d78d2f9817fe643aadba39aa56",
   "date": "2025-11-04T13:56:45Z",
   "message": "net: split CConnman::AcceptConnection() off CConnman\n\nMove the `CConnman::AcceptConnection()` method to `SockMan` and split\nparts of it:\n* the flip-to-CJDNS part: to just after the `AcceptConnection()` call\n* the permissions part: at the start of `CreateNodeFromAcceptedSocket()`"
  },
  {
   "sha": "95265620ce76daa068ebc1075292f78f1f4b080b",
   "date": "2025-11-04T13:56:45Z",
   "message": "style: modernize the style of SockMan::AcceptConnection()"
  },
  {
   "sha": "44d4b620f4498530d1e21d12b29103c192ddf47d",
   "date": "2025-11-04T13:56:46Z",
   "message": "net: move the generation of ids for new nodes from CConnman to SockMan\n\nMove `CConnman::GetNewNodeId()` to `SockMan::GetNewId()`. Avoid using\nthe word \"node\" because that is too specific for `CConnman`."
  },
  {
   "sha": "07d731b25219cd27a9428d443902f450acc9423c",
   "date": "2025-11-04T13:56:47Z",
   "message": "net: move CConnman-specific parts away from ThreadI2PAcceptIncoming()\n\nCConnman-specific or in other words, Bitcoin P2P specific. Now\nthe `ThreadI2PAcceptIncoming()` method is protocol agnostic and\ncan be moved to `SockMan`."
  },
  {
   "sha": "3e91c8b502d43d656d9f3207cb550a0c03d1429e",
   "date": "2025-11-04T13:56:47Z",
   "message": "net: move I2P-accept-incoming code from CConnman to SockMan"
  },
  {
   "sha": "63751341d1ba7ba144e221bb8f25fec6293b08b3",
   "date": "2025-11-04T13:56:48Z",
   "message": "net: index nodes in CConnman by id\n\nChange `CConnman::m_nodes` from `std::vector<CNode*>` to\n`std::unordered_map<NodeId, CNode*>` because interaction\nbetween `CConnman` and `SockMan` is going to be based on\n`NodeId` and finding a node by its id would better be fast.\n\nChange `PeerManagerImpl::EvictExtraOutboundPeers()` to account for nodes\nno longer always being in order of id. The old code would have failed to\nupdate `next_youngest_peer` correctly if `CConnman::m_nodes` hadn't\nalways had nodes in ascending order of id.\n\nDuring fuzzing make sure that we don't generate duplicate `CNode` ids.\nThe easiest way to do that is to use sequential ids.\n\nAs a nice side effect the existent search-by-id operations in\n`CConnman::AttemptToEvictConnection()`,\n`CConnman::DisconnectNode()` and\n`CConnman::ForNode()` now become `O(1)` (were `O(number of nodes)`),\nas well as the erase in `CConnman::DisconnectNodes()`."
  },
  {
   "sha": "3bb0f2ddfb88e6b8d1aa5cff77416630ad11a381",
   "date": "2025-11-04T13:56:49Z",
   "message": "net: isolate P2P specifics from GenerateWaitSockets()\n\nMove the parts of `CConnman::GenerateWaitSockets()` that are specific to\nthe Bitcoin-P2P protocol to dedicated methods:\n`ShouldTryToSend()` and `ShouldTryToRecv()`.\n\nThis brings us one step closer to moving `GenerateWaitSockets()` to the\nprotocol agnostic `SockMan` (which would call `ShouldTry...()` from\n`CConnman`)."
  },
  {
   "sha": "7765f94b1665e2c7ccd0ad5a4ca017817b6c7ed4",
   "date": "2025-11-04T13:56:49Z",
   "message": "net: isolate P2P specifics from SocketHandlerConnected() and ThreadSocketHandler()\n\nMove some parts of `CConnman::SocketHandlerConnected()` and\n`CConnman::ThreadSocketHandler()` that are specific to the Bitcoin-P2P\nprotocol to dedicated methods:\n`EventIOLoopCompletedForOne(id)` and\n`EventIOLoopCompletedForAll()`.\n\nThis brings us one step closer to moving `SocketHandlerConnected()` and\n`ThreadSocketHandler()` to the protocol agnostic `SockMan` (which would\ncall `EventIOLoopCompleted...()` from `CConnman`)."
  },
  {
   "sha": "820c157ebbaeffc9af4ce82ddf5be62830db629c",
   "date": "2025-11-04T13:56:50Z",
   "message": "net: isolate all remaining P2P specifics from SocketHandlerConnected()\n\nIntroduce 4 new methods for the interaction between `CConnman` and\n`SockMan`:\n\n* `EventReadyToSend()`:\n  called when there is readiness to send and do the actual sending of data.\n\n* `EventGotData()`, `EventGotEOF()`, `EventGotPermanentReadError()`:\n  called when the corresponing recv events occur.\n\nThese methods contain logic that is specific to the Bitcoin-P2P protocol\nand move it away from `CConnman::SocketHandlerConnected()` which will\nbecome a protocol agnostic method of `SockMan`.\n\nAlso, move the counting of sent bytes to `CConnman::SocketSendData()` -\nboth callers of that method called `RecordBytesSent()` just after the\ncall, so move it from the callers to inside\n`CConnman::SocketSendData()`."
  },
  {
   "sha": "b1fc18e505d862763a8a8a902637b57d107626b7",
   "date": "2025-11-04T13:57:49Z",
   "message": "net: split CConnman::ConnectNode()\n\nMove the protocol agnostic parts of `CConnman::ConnectNode()` into\n`SockMan::ConnectAndMakeId()` and leave the Bitcoin-P2P specific\nstuff in `CConnman::ConnectNode()`.\n\nMove the protocol agnostic `CConnman::m_unused_i2p_sessions`, its mutex\nand `MAX_UNUSED_I2P_SESSIONS_SIZE` to `SockMan`.\n\nMove `GetBindAddress()` from `net.cpp` to `sockman.cpp`."
  },
  {
   "sha": "44f4fed343efabe9468aaa519a38707ec6151fea",
   "date": "2025-11-04T13:57:55Z",
   "message": "net: tweak EventNewConnectionAccepted()\n\nMove `MaybeFlipIPv6toCJDNS()`, which is Bitcoin P2P specific from the\ncallers of `CConnman::EventNewConnectionAccepted()` to inside that\nmethod.\n\nMove the IsSelectable check, the `TCP_NODELAY` option set and the\ngeneration of new connection id out of\n`CConnman::EventNewConnectionAccepted()` because those are protocol\nagnostic. Move those to a new method `SockMan::NewSockAccepted()` which\nis called instead of `CConnman::EventNewConnectionAccepted()`."
  },
  {
   "sha": "a59b12aba139aa6003e65ef5842f537a99ae4340",
   "date": "2025-11-04T13:57:55Z",
   "message": "net: move sockets from CNode to SockMan\n\nMove `CNode::m_sock` and `CNode::m_i2p_sam_session` to `SockMan::m_connected`.\nAlso move all the code that handles sockets to `SockMan`.\n\n`CNode::CloseSocketDisconnect()` becomes\n`CConnman::MarkAsDisconnectAndCloseConnection()`.\n\n`CConnman::SocketSendData()` is renamed to\n`CConnman::SendMessagesAsBytes()` and its sockets-touching bits are moved to\n`SockMan::SendBytes()`.\n\n`CConnman::GenerateWaitSockets()` goes to\n`SockMan::GenerateWaitSockets()`.\n\n`CConnman::ThreadSocketHandler()` and\n`CConnman::SocketHandler()` are combined into\n`SockMan::ThreadSocketHandler()`.\n\n`CConnman::SocketHandlerConnected()` goes to\n`SockMan::SocketHandlerConnected()`.\n\n`CConnman::SocketHandlerListening()` goes to\n`SockMan::SocketHandlerListening()`."
  },
  {
   "sha": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "date": "2025-11-04T13:57:56Z",
   "message": "net: move-only: improve encapsulation of SockMan\n\n`SockMan` members\n\n`AcceptConnection()`\n`NewSockAccepted()`\n`m_i2p_sam_session`\n`m_listen`\n\nare now used only by `SockMan`, thus make them private.\n\n`GetNewId()` is used by `SockMan` and in tests, so make it protected."
  }
 ],
 "timeline": [
  {
   "t": "2024-09-27T08:41:33Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/rpc/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "nit: Arrays are ordered data structures and I don't think it is user-friendly to shuffle them by ID. I think this comment can just be removed."
  },
  {
   "t": "2024-09-27T11:41:28Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Nice! I'll try to use this for `Sv2Connman` in https://github.com/Sjors/bitcoin/pull/50 and will let you know if anything is missing.\n\nCan you put `sockman.h` in `libbitcoin_common` instead of `libbitcoin_node`? For the Template Provider I'm trying to prevent a circular dependency on the node. This should do the trick: https://github.com/bitcoin/bitcoin/commit/4dd51b2924860bf10466da080ea4ff7bed1a3e3f"
  },
  {
   "t": "2024-09-27T15:05:31Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "70c2f13f83a5cc740330d0b4af9cbd74515be6b2"
  },
  {
   "t": "2024-09-27T15:05:41Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/rpc/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1778257807,
   "text": "Removed, thanks!"
  },
  {
   "t": "2024-09-27T15:07:28Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`03f6cc2b4a...70c2f13f83`: fix CI failure, and address suggestions\n\n[quoted text omitted]\nDone."
  },
  {
   "t": "2024-09-27T16:51:49Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Here's an initial sketch of making `Sv2Connman` a subclass of `SockMan`. The test gets through the handshake but fails later on, so I'll need to study it a bit more closely.\n\nhttps://github.com/Sjors/bitcoin/pull/64"
  },
  {
   "t": "2024-09-29T22:05:50Z",
   "kind": "review",
   "who": "tdb3",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "70c2f13f83a5cc740330d0b4af9cbd74515be6b2",
   "text": "Concept ACK"
  },
  {
   "t": "2024-09-30T13:38:34Z",
   "kind": "comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "text": "Concept ACK\n\nThis looks great and the API in the header looks easy, thanks.\n\nI'm in the process of cleaning up my HTTP branch for a pull request and then I can start reviewing this and rebasing on top.\n\nOne element of libevent I'm not immediately seeing here is timed events. Really the only thing HTTP needs it for is `walletpassphrase` which calls `RPCRunLater()` which interacts with `HTTPRPCTimerInterface()`. I don't think Conman has a specific mechanism for this because timed things are attached directly to nodes like `m_last_getheaders_timestamp` etc. The current `HTTPRPCTimerInterface` uses libevent `event_new()`  and `evtimer_add()`, I accomplish this with a map of timestamps and callback functions in my event loop: https://github.com/pinheadmz/bitcoin/commit/42b7240378ffc4890ab4ba1453623e5986ea2a71"
  },
  {
   "t": "2024-09-30T14:07:19Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI wonder why the existing scheduler can't be used for re-locking the wallet? I know there is https://github.com/bitcoin/bitcoin/issues/18488 and https://github.com/bitcoin/bitcoin/issues/14289, but the thread is already filled with random stuff such as `BerkeleyDatabase::PeriodicFlush()`, and relocking the wallet seems(?) fast (I haven't benchmarked), so should be fine to put in there as well, at least from that perspective?"
  },
  {
   "t": "2024-10-03T10:07:55Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "@pinheadmz, I think that the functionality of \"execute this code after some time\", is not much related to the sockets handling and better be implemented at some higher level, not inside `SockMan`. Maybe the scheduler, like @maflcko suggested, or in the `EventIOLoopCompletedForAllPeers()` method which will be called periodically by `SockMan`:\n\n```cpp\n    /**\n     * SockMan has completed send+recv for all nodes.\n     * Can be used to execute periodic tasks for all nodes.\n     * The implementation in SockMan does nothing.\n     */\n    virtual void EventIOLoopCompletedForAllPeers();\n```\n\nEdit: I guess `TriggerEvents()` from https://github.com/pinheadmz/bitcoin/commit/42b7240378ffc4890ab4ba1453623e5986ea2a71 can be called from `EventIOLoopCompletedForAllPeers()` or from the scheduler."
  },
  {
   "t": "2024-10-04T13:54:41Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "@vasild if you rebase past #31011, tidy might point out that `sockman.cpp.o depends on i2p.cpp`. So you probably need to either move `i2p.cpp` to common as well, or remove the dependency."
  },
  {
   "t": "2024-10-05T04:02:01Z",
   "kind": "review",
   "who": "Jacksonearl2468",
   "assoc": "NONE",
   "state": "APPROVED",
   "commit": "70c2f13f83a5cc740330d0b4af9cbd74515be6b2",
   "text": ""
  },
  {
   "t": "2024-10-16T11:15:53Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "Silent merge conflict with #30937"
  },
  {
   "t": "2024-10-16T19:27:15Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "1bfc1ca9b6b68c6356ffc23ecf01e417152ade95\n\nWhy do CNode .addr and .addrBind still need to be CAddress? maybe it makes some sense for .addr if that's where we also store the service flags for easier gossiping, but why would the local bind address need anything besides an IP and port?"
  },
  {
   "t": "2024-10-16T19:38:45Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "41c87ddb3d7a18d2c0fa7eccfbde57e9d6e898c2\n\nMaybe these are expected to be set by callers in future commits, so I'm just leaving myself a note to look for `SO_KEEPALIVE` and `TCP_NODELAY`"
  },
  {
   "t": "2024-10-17T14:53:34Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "5d4920f630417ceeee1c79304faac6803795cd64\n\nI don't really have any better suggestion for this but the term \"node\" is now a higher-level ConnMan context, for HTTP and StratumV2 I'd probably refer to them as \"client\"."
  },
  {
   "t": "2024-10-17T15:11:34Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "b94f9d338fd0d3b47a4ea6165ce9cde48f3b19bc\n\nReminding myself to check for a test that covers a child class with i2p true but no Event listener"
  },
  {
   "t": "2024-10-17T15:19:48Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1803692315,
   "text": "Ok I see NODELAY set  by ConnMan in EventNewConnectionAccepted in b94f9d338fd0d3b47a4ea6165ce9cde48f3b19bc\n\nI'm guessing we dont use KEEPALIVE for bitcoin p2p because we have out own ping/pong protocol"
  },
  {
   "t": "2024-10-17T15:29:01Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "b94f9d338fd0d3b47a4ea6165ce9cde48f3b19bc\n\nSeems weird to me to move the unique pointer to a connected socket out of sockman, implying that sockman itself doesn't own the connections. Peeking ahead at future commits though I think this gets resolved."
  },
  {
   "t": "2024-10-17T16:52:54Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/net.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "b96beb27d2b3d06a45644c537c114a08f0ccd285\n\nLove this, great optimization and makes a lot of sense."
  },
  {
   "t": "2024-10-17T16:55:41Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/connman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "b96beb27d2b3d06a45644c537c114a08f0ccd285\n\nWhy not use `GetNewNodeId()` here? (and below) Don't you have a Connman with its own `m_next_node_id`?"
  },
  {
   "t": "2024-10-17T17:03:08Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "bb5b91d430026c4826a2107c8c1a311518cc97ce\n\nnit s/temporary/temporarily\n\nand above"
  },
  {
   "t": "2024-10-17T17:12:00Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "50cb52470ea6cc4de5a5e5260b8f308353942ec0\n\nThis is great, can be used for timed actions (like locking the wallet) we used to depend on libevent for"
  },
  {
   "t": "2024-10-17T17:19:06Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "50cb52470ea6cc4de5a5e5260b8f308353942ec0\n\nInteresting that the parent class handles accepting connections but the child is expected to handle disconnections, and inside the utility event loop function. Of course only the derived class would know when disconnection is appropriate, I wonder if anything it makes sense just to add a comment in `sockman.h` that `EventIOLoopCompletedForAllPeers()` is expected to handle that, erasing items from `m_nodes`"
  },
  {
   "t": "2024-10-17T17:32:07Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "3bb0145514978092ae966e30693cf619e4034837\n\nthe `else {error}` looked backwards to me here at first, might be more clear to use `== 0` instead of `!` but i dunno what the official style is for this."
  },
  {
   "t": "2024-10-17T17:35:04Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "3bb0145514978092ae966e30693cf619e4034837\n\nWould this make more sense as a direct method of `Sock` ? Especially now that it returns the simpler `CService` instead of `CAddress`"
  },
  {
   "t": "2024-10-17T17:44:51Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "3bb0145514978092ae966e30693cf619e4034837\n\nThis is great. Won't be needed immediately for HTTP or StratumV2 but means we can more easily remove libevent from `bitcoin-cli` !"
  },
  {
   "t": "2024-10-17T18:04:36Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1803692315,
   "text": "Moved to `NewSockAccepted()` in 9d1b352a4d050ec88b59cdf59209c0f086fea4e6"
  },
  {
   "t": "2024-10-17T18:22:07Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1804998403,
   "text": "resolved in b94f9d338fd0d3b47a4ea6165ce9cde48f3b19bc"
  },
  {
   "t": "2024-10-18T14:57:30Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "c133a634d10e172652ebbf05ca114037c8e551b9\n\nI know this is for testing only, but what happens if there is a NodeId collision (An existing node is inserted with a duplicate ID, replacing a connected node in sockman)?"
  },
  {
   "t": "2024-10-18T15:13:32Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "c133a634d10e172652ebbf05ca114037c8e551b9\n\nYou might want to add another comment here - when I was trying to handle socks myself I found this a bit weird and wrote:\n\n```\n    // WaitMany() mine as well be a static function, the context\n    // of the first Sock in the vector is not relevant.\n```"
  },
  {
   "t": "2024-10-18T15:14:27Z",
   "kind": "review",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "70c2f13f83a5cc740330d0b4af9cbd74515be6b2",
   "text": "ACK 70c2f13f83a5cc740330d0b4af9cbd74515be6b2\n\nBuilt and ran tests on macos/arm as well as debian/x86.\n\nSynced to signet with this build on a debian server, then synced from that node locally using the macos build.\n\nI left a lot of comments below but most are for myself to track during rebases, indicate to the maintainers the depth that I'm actually reviewing, and also some notes for myself -- I'll be rebaing my non-libevent http server on this banch and consuming the new SockMan.\n\nI have a working branch with my own sock handler for http, so I was able to compare that work to the code in this PR to follow along.\n\nThere are two areas I am not entirely clear on that I will review more closely in the coming weeks which is I2P stuff, and reviewing all the mutex stuff.\n\nShow Signature\n\n```\n-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\nACK 70c2f13f83a5cc740330d0b4af9cbd74515be6b2\n-----BEGIN PGP SIGNATURE-----\n\niQIzBAEBCAAdFiEE5hdzzW4BBA4vG9eM5+KYS2KJyToFAmcSeTIACgkQ5+KYS2KJ\nyTp/1A//ZzhksZzWlBbV6rcfIXnUVQS0SIgmY7sZCN8AN0RoiN4ECALWyrXz9Rdg\nfONCWiQSTyxE5u2TnFo7os+o0Z+3ybxApufAx/OzqHaJwR8d5wnZPHU/uFTPYm/z\nqArPT3SHQgKb1sftt8S5ve7j37RW7IbshiJ+6bq8+DNMY1Ep1DX4BBzZgwbzx2ek\nqMcRFQkOkTbJg3/yOT1Vz361qCPLmbKjvbRyjYgYYG7oxtdwb5r/ezeagSCWKDJo\nYx/DJ2cwZ3uMWDvfnhNJOeql4MhOLZcU5hKyGGTRT3B5ER7ELNye5+Sz+pv0cJuP\nR+zfoxAfYV3opqB/ewTD1sM+gtFZFUiLNHigPgKFmK5GerLguXUqY+vgQ/4VIEpg\nXD2tHOCwwDULsekVLZ+hXJPSTBDmZAlHmJvcCBQgzLkjCMvrlmxJR8KpKg3lDgKM\nGwFcYoIWubyFKgRCAQL6vc1qFnGc7PJ1TWJLoKxpjXeyE6igHquSK0KE3flhCJ97\nCpWhZflrHwcaomSf9bDYsSaNvMN2MiFUzC4lbmLRWTzeOCS9oMFRMcIayQ+psUqC\n0gmbZzi+Qcf32gCNYvhxYwKqTeAlKih9x+vurTuK8831AZoUYwusetGaTsklwvOn\nbDpgEGoqFPdPIB8yHAiFBwQPcGmn7tVmTbVhck59B3HvdS/wOr4=\n=rnjt\n-----END PGP SIGNATURE-----\n```\n\npinheadmz's public key is [on keybase](https://keybase.io/pinheadmz/pgp_keys.asc?fingerprint=e61773cd6e01040e2f1bd78ce7e2984b6289c93a)"
  },
  {
   "t": "2024-10-21T15:09:15Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "cc @theuni any takes?"
  },
  {
   "t": "2024-11-14T16:47:02Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "This needs a rebase. Otherwise it's non-trivial for me to keep https://github.com/Sjors/bitcoin/pull/67 rebased.\n\nConflicts:\n* https://github.com/bitcoin/bitcoin/pull/30937"
  },
  {
   "t": "2024-11-14T16:55:15Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "c4f51c7f61e6c4e291025e450f6dd97c2b939b56"
  },
  {
   "t": "2024-11-14T16:58:07Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`70c2f13f83...c4f51c7f61`: rebase due to conflicts, next I will look at @pinheadmz feedback above and implement changes from coredev discussions:\n\n* Store the node id -> CNode map in SockMan (then the SockMan class becomes templated)\n* SockMan <-> Connman calls based on CNode, not node id\n* Make the virtual methods in SockMan private, since they are called only from SockMan"
  },
  {
   "t": "2024-11-19T13:15:12Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "2b0103705fefddff6b880b9eb72fb590740a7300"
  },
  {
   "t": "2024-11-19T13:28:24Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`c4f51c7f61...2b0103705f`: rebase and make the virtual methods of `SockMan` private since they are only used by `SockMan`.\n\nAbout storing the `CNode` in `SockMan` (which would be templated like `SockMan<CNode>`) and making the communication between `SockMan` and the higher class (e.g. `CConnman`) based on `CNode` instead of node id: this is an excellent idea that will make the code more straight-forward and the higher classes simpler. However it would make this PR larger. I will leave it off for now. There is some specific `CNode` ref-counting in `CConnman` and the code around deleting a node is a bit complicated, so it would require further non-trivial changes to move that to `SockMan`.\n\nI think in general, in the long term, independently of this PR, it would be good to get rid of the manual `CNode` ref-counting and tap `std::shared_ptr` to do that for us. In other words, to revive https://github.com/bitcoin/bitcoin/pull/28222."
  },
  {
   "t": "2024-12-03T12:43:35Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1803679987,
   "text": "`CNode::addr` needs to be `CAddress` because at least `PeerManagerImpl::PushNodeVersion()` is using `addr.nServices`.\n\n`CNode::addrBind` indeed does not need to be `CAddress`. I changed it to `CService`."
  },
  {
   "t": "2024-12-03T12:46:38Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1804940161,
   "text": "\"Peer\" is a good description for this, but that is already used in `net_processing`."
  },
  {
   "t": "2024-12-03T13:10:42Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/connman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1805115729,
   "text": "`GetNewNodeId()` is private. Not worth weakening the encapsulation for this test."
  },
  {
   "t": "2024-12-03T13:13:11Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1805124507,
   "text": "Done"
  },
  {
   "t": "2024-12-03T13:34:07Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1805142536,
   "text": "Good observation. I guess this is the nature of things: accepting new connections is driven (initiated) by low level socket event when a new connection arrives, while disconnecting can be low level driven or high level driven - e.g. misbehaving peer at the application layer.\n\nI extended the comment of `EventIOLoopCompletedForAllPeers()` a little bit:\n\n```diff\n    -+     * Can be used to execute periodic tasks for all nodes.\n    ++     * Can be used to execute periodic tasks for all nodes, like disconnecting\n    ++     * nodes due to higher level logic.\n```"
  },
  {
   "t": "2024-12-03T13:41:11Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1805157675,
   "text": "I don't think we have an official recommendation. My personal preference is to not treat integers as booleans: use `if (x == 0)` instead of `if (!x)` when `x` is an integer. But I am leaving this as it is because it is like that in `master` and this PR only moves it from `net.cpp` to `sockman.cpp` verbatim. I hope it is easier to reviewers to just check that a blob of code is moved without having to validate its correctness since that same code is already in `master`. This helps with such moves:\n\n`~/.gitconfig`:\n```\n[diff]\n        colorMoved = dimmed-zebra\n        colorMovedWS = allow-indentation-change\n```"
  },
  {
   "t": "2024-12-03T13:46:07Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1805160999,
   "text": "Maybe yes, but `sock.{h,cpp}` does not currently include `netaddress.h` and I guess including it may cause circular dependency. It would also increase the scope of this PR. I am leaving it as it is."
  },
  {
   "t": "2024-12-03T13:53:20Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1806645938,
   "text": "It would be a mess. I added an assert to make it clear that a test which inserts duplicate ids is broken."
  },
  {
   "t": "2024-12-03T13:57:46Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1806666862,
   "text": "Done"
  },
  {
   "t": "2024-12-03T13:59:28Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "645f625e29b36321bcba8db5758b0428b8ebd80f"
  },
  {
   "t": "2024-12-03T13:59:38Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`2b0103705f...645f625e29`: rebase and address suggestions"
  },
  {
   "t": "2024-12-10T20:12:05Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "Why not use std::span? I thought `Span` was only a temporary fill-in until C++20 something something? Also, I'm using std::span elsewhere... is `Span` the preference?"
  },
  {
   "t": "2024-12-16T07:10:15Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1878764170,
   "text": "Even in relatively new code folks often use `Span`, e.g. #30043. cc @sipa, @laanwj"
  },
  {
   "t": "2024-12-19T02:07:54Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Looks like the merge conflict is with #31072 as well as #31223."
  },
  {
   "t": "2024-12-19T05:47:48Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1878764170,
   "text": "Must have been just inertia. The caller of the newly added `SendBytes()` has an argument that is already `Span` from elsewhere in the code, coming from here:\n\nhttps://github.com/bitcoin/bitcoin/blob/c1252b14d714448295200a595086dd3e78b46c8f/src/net.h#L307-L311\n\nNow I changed the argument of the newly added `SendBytes()` to `std::span`, looks better and the `Span` variable is accepted. Thanks!\n\nwen `s/Span/std::span/` all over the place?"
  },
  {
   "t": "2024-12-19T05:48:11Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "b8b042626ea65c2c2b355a6de69cb7c98bd04de3"
  },
  {
   "t": "2024-12-19T05:48:57Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`645f625e29...b8b042626e`: rebase due to conflicts and use `std::span` instead of `Span` in new code: https://github.com/bitcoin/bitcoin/pull/30988#discussion_r1878764170"
  },
  {
   "t": "2024-12-19T18:36:33Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1878764170,
   "text": "[quoted text omitted]\n\nnow: https://github.com/bitcoin/bitcoin/pull/31519"
  },
  {
   "t": "2025-01-13T14:31:15Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "f71f1a346c71909750e5e7d11305dd0a1c24373c"
  },
  {
   "t": "2025-01-13T14:31:34Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`b8b042626e...f71f1a346c`: rebase due to conflicts"
  },
  {
   "t": "2025-01-14T14:09:51Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "I found myself confused by e8b589ef45d5c94c47209fb952e8b05d631a9c47 dropping `AddSocketPermissionFlags`. Can you move that to a separate (earlier?) commit?"
  },
  {
   "t": "2025-01-14T15:05:33Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "4c23aacc2b82083f96255963f543dc35fff28334: I'm inclined to say \"listening **on** %s\"\n\nAs in: we bind _to_ a port in order to listen _on_ it.\n\nThe linux manual for `listen` also uses \"on\", as in \"listen for connections on a socket\"\nhttps://man7.org/linux/man-pages/man2/listen.2.html"
  },
  {
   "t": "2025-01-14T15:15:03Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "4c23aacc2b82083f96255963f543dc35fff28334: since you need to print log statements here in a few places anyway, might as well drop the `errmsg` argument and always print logs here."
  },
  {
   "t": "2025-01-14T16:38:24Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1915015927,
   "text": "`BindAndStartListening()` needs to return an error string to the caller, because the caller not only logs it but also passes it to `ThreadSafeMessageBox()`."
  },
  {
   "t": "2025-01-14T16:38:52Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1914992602,
   "text": "Done, `s/at/on/`"
  },
  {
   "t": "2025-01-14T16:39:29Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "d83ff66fd74654e67b56d11a7d8e92d38a4bd17a"
  },
  {
   "t": "2025-01-14T16:40:02Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`f71f1a346c...d83ff66fd7`:\n\n[quoted text omitted]\nDone."
  },
  {
   "t": "2025-01-14T16:45:41Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1915015927,
   "text": "Ah ok, I missed that bit."
  },
  {
   "t": "2025-01-14T17:37:26Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "bcf1254e91782a33b5a1db542f3758831e0bfe2e"
  },
  {
   "t": "2025-01-14T17:41:46Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`d83ff66fd7...bcf1254e91`: adjust test after change of the log message in the previous push"
  },
  {
   "t": "2025-01-15T11:18:27Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "fd81820214e695ba228a954506397c3d781fe3fe: do want to add an `Assume` here, given that `Bind` always adds an entry?"
  },
  {
   "t": "2025-01-15T11:28:21Z",
   "kind": "review",
   "who": "Sjors",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "bcf1254e91782a33b5a1db542f3758831e0bfe2e",
   "text": "[quoted text omitted]\n\nThanks. Don't forget to drop \"Also, separate the listening socket from the permissions\" from the commit description of e5d36eea015efc31aa38d540af4cf39c9e2e46b0.\n\nAlong similar lines, though less important, you could also have a separate commit that drops `ListenSocket` and introduces `m_listen` as a member of `CConnman`, before moving it to `Sockman`.\n\n(it makes `git show --color-moved=dimmed-zebra` more grey, less red and green when you introduce `SockMan`)"
  },
  {
   "t": "2025-01-15T11:54:18Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "0241b04cf406d482abfac3fddfad9a9c28725f32: previously this would not get called if `addr.SetSockAddr` failed in `AcceptConnection`. I suspect it doesn't matter though."
  },
  {
   "t": "2025-01-15T12:01:48Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1804940161,
   "text": "1b05e1d4ba55a42ba74026b68fa4e616b973e06d: maybe `ConnectionId`?\n\nAnd then in net.h use `typedef ConnectionId NodeId;`\n\nhttps://github.com/Sjors/bitcoin/commit/f33049b45b7013022ed4c75c0bc52d878fe15fd5\n\nMost of the churn would be in `sockman.{h,cpp}`, so that seems acceptable.\n\nThat said, I don't think it's very important."
  },
  {
   "t": "2025-01-15T12:37:21Z",
   "kind": "review",
   "who": "Sjors",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "bcf1254e91782a33b5a1db542f3758831e0bfe2e",
   "text": "Reviewed up to 1b05e1d4ba55a42ba74026b68fa4e616b973e06d."
  },
  {
   "t": "2025-01-15T13:17:31Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "14fcef6b0d1d1fa9395f9af2bafbf3de63d14ac2: I find this confusing.\n\nI think the problem starts with the strange naming of `ThreadI2PAccept`, instead of `ThreadI2PHandler` akin to `ThreadSocketHandler`.\n\nAnyway, what that thread seems to do is, in a loop, try to `Listen` on the socket and once listening, `Accept(` new connections.\n\nWhenever listening fails or succeeds it calls `EventI2PListen` which then calls `AddLocal` / `RemoveLocal` to keep the announced public addresses up to date.\n\nWith that in mind, I would call this new method `EventI2PConnectivity` and rename `success` to either `connected` or `listening`. Then the rest makes sense."
  },
  {
   "t": "2025-01-15T13:59:48Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/net.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "21c5e05619c8a6eb736bb1c61725f4b5f669ffb4: maybe use `[_, node]` in places where you don't need the `id`.\n\nIt's a bit unfortunate that you can't (?) directly loop over all `T` in `std::unordered_map<Key, T>`. Since most of the time we don't need `Key` (`id`) here."
  },
  {
   "t": "2025-01-15T14:10:09Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "dc6393cb93c4851a363b69fd474656cac1ae3b3b: `EventReadyToSend` doesn't exist yet in this commit, so maybe the commit message can announce it."
  },
  {
   "t": "2025-01-15T14:16:14Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "dc6393cb93c4851a363b69fd474656cac1ae3b3b: it would be good to annotate in `CConnman::SocketHandlerConnected` above `sendSet = it->second.occurred & Sock::SEND;` that `Sock::SEND` is unset when `ShouldTryToRecv` is `false`."
  },
  {
   "t": "2025-01-15T14:25:01Z",
   "kind": "review",
   "who": "Sjors",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "bcf1254e91782a33b5a1db542f3758831e0bfe2e",
   "text": "Concept ACK"
  },
  {
   "t": "2025-01-17T17:47:59Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "Does this hard-coded thread name cause any issues if sockman is used more than once?"
  },
  {
   "t": "2025-01-23T16:18:36Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "I might be misunderstanding `cancel_recv`, in my HTTP server I set it to `true` when I'm planning on disconnecting a node like if there was an error sending data or if `\"Connection: close\"` is set. However this causes weird behavior where new connections get hung up forever. In particular, `interface_rpc.py` stalls on the ultimate node shutdown and eventually times out after 120 seconds of rpc `stop` not working. When I set `cancel_recv=false` everywhere, i don't have this issue. (Still a lot of other issues to clean up in this branch!)\n\nhttps://github.com/pinheadmz/bitcoin/commit/df1965a4e97ce7f3df067df9b3dcc2bbd8e6e15b"
  },
  {
   "t": "2025-01-28T13:49:17Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1916382790,
   "text": "Right, currently `Bind()` always adds an entry, but I do not want to impose that because the code here does not require it. It is fine if there is nothing in `m_listen_permissions`. Then the permissions will remain at `NetPermissionFlags::None`. So it is kind of optional for `Bind()` to add an entry to `m_listen_permissions` and no reason to enforce it."
  },
  {
   "t": "2025-01-28T14:01:14Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1916431512,
   "text": "Yes, good observation! Indeed the flip will do nothing on the invalid `0.0.0.0` `addr_accepted`.\n\nThe code in `master` is also a bit odd that it continues if `addr.SetSockAddr()` fails in `CConnman::AcceptConnection()` and it will pass the default constructed, invalid `0.0.0.0` address to `CreateNodeFromAcceptedSocket()`. I didn't change that in this PR as it would be a functional change that is not needed for this PR."
  },
  {
   "t": "2025-01-28T15:09:34Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1916636569,
   "text": "`ThreadI2PAccept` vs `ThreadI2PHandler`. The thing is that this thread is only accepting connections. It does not do anything about I2P connections that are already established. `ThreadSocketHandler` does both accepting new connections and doing send/recv on existent connections.\n\nYes, your observation is correct. Listening can fail temporary, e.g. if the I2P router daemon is restarted. This is unlike the TCP listening where once `bind()` and `listen()` succeed, then we are listening until we decide to close the socket. Tor is like I2P - it can fail to listen for a while if the Tor daemon is restarted. Our Tor code has the deficiency that it does not handle that, so it would act as if we are listening (e.g. advertising our Tor address) even if we are not listening.\n\nCurrent `EventI2PListen` vs `EventI2PConnectivity` - I think \"Connectivity\" would be too broad because this method is only called in the event where we start or stop listening for incoming I2P connections. It is not related to whether we have existent I2P connections or to our ability to make new outgoing I2P connections which \"Connectivity\" would imply."
  },
  {
   "t": "2025-01-28T15:41:46Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1916636569,
   "text": "Maybe `EventI2PReady` or `EventI2PStatus`? Something to distinguish it from listen."
  },
  {
   "t": "2025-01-28T16:19:53Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1916699333,
   "text": "Changed to `[_, node]`, thanks! :)"
  },
  {
   "t": "2025-01-28T16:33:39Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1916714492,
   "text": "Right! Adjusted the comments."
  },
  {
   "t": "2025-01-28T16:47:52Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1916723639,
   "text": "Hmm, you probably mean `ShouldTryToSend` is `false`, right? And then the same applies to receiving. Amended this:\n\n```diff\n-                recvSet = it->second.occurred & Sock::RECV;\n-                sendSet = it->second.occurred & Sock::SEND;\n+                recvSet = it->second.occurred & Sock::RECV; // Sock::RECV could only be set if ShouldTryToRecv() has returned true in GenerateWaitSockets().\n+                sendSet = it->second.occurred & Sock::SEND; // Sock::SEND could only be set if ShouldTryToSend() has returned true in GenerateWaitSockets().\n```"
  },
  {
   "t": "2025-01-28T17:15:25Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1920546152,
   "text": "Yes, it would mess up the logging. I put both `\"net\"` and `\"i2paccept\"` in `SockMan::Options`, so that they are not hardcoded here anymore and can be set by the callers of `SockMan`."
  },
  {
   "t": "2025-01-28T17:18:56Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "266ac3267390270e6794d478aa1fbe40e88ffb0f"
  },
  {
   "t": "2025-01-28T17:20:15Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`bcf1254e91...266ac32673`: address suggestions\n\n[quoted text omitted]\nDone.\n\n[quoted text omitted]\nDone.\n\nThanks!"
  },
  {
   "t": "2025-01-28T19:36:29Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1927267574,
   "text": "[quoted text omitted]\n\nI think the problem was that `HTTPServer::EventReadyToSend()` could end up not setting `cancel_recv` in which case it remains uninitialized, containing a \"random\" value from the stack at the caller (which was probably `true`, so it canceled all receives from the client, never receiving anything causing an infinite loop):\n\n```cpp\n              bool cancel_recv;\n\n              EventReadyToSend(node_id, cancel_recv);\n\n              if (cancel_recv) {\n                  recv_ready = false;\n              }\n```\n\nValgrind or memory sanitizer should complain about this code reading uninitialized value in `if (cancel_recv)`.\n\nThe below fixes it, remove the last commit from your branch and move the setting of `cancel_recv` from the bottom to the top of the method. So that `cancel_recv` is set in all code paths inside `HTTPServer::EventReadyToSend()`.\n\n```diff\n--- i/src/httpserver.cpp\n+++ w/src/httpserver.cpp\n@@ -1123,12 +1123,15 @@ bool HTTPServer::EventNewConnectionAccepted(NodeId node_id,\n     m_connected_clients.emplace(client->m_node_id, std::move(client));\n     return true;\n }\n\n void HTTPServer::EventReadyToSend(NodeId node_id, bool& cancel_recv)\n {\n+    // Next attempt to receive data from this node is permitted\n+    cancel_recv = false;\n+\n     // Get the HTTPClient\n     auto client{WITH_LOCK(m_clients_mutex, return GetClientById(node_id);)};\n     if (client == nullptr) {\n         return;\n     }\n\n@@ -1149,26 +1152,26 @@ void HTTPServer::EventReadyToSend(NodeId node_id, bool& cancel_recv)\n             LogDebug(\n                 BCLog::HTTP,\n                 \"Error sending HTTP response headers to client %s (id=%lld): %s\\n\",\n                 client->m_origin,\n                 client->m_node_id,\n                 err);\n-            cancel_recv = false;\n+            cancel_recv = true;\n             client->m_disconnect = true;\n             return;\n         }\n\n         ssize_t body_bytes_sent = SendBytes(node_id, MakeUCharSpan(res.m_body), /*will_send_more=*/true, err);\n         if (body_bytes_sent < 0) {\n             LogDebug(\n                 BCLog::HTTP,\n                 \"Error sending HTTP response body to client %s (id=%lld): %s\\n\",\n                 client->m_origin,\n                 client->m_node_id,\n                 err);\n-            cancel_recv = false;\n+            cancel_recv = true;\n             client->m_disconnect = true;\n             return;\n         }\n\n         LogDebug(\n             BCLog::HTTP,\n@@ -1176,20 +1179,17 @@ void HTTPServer::EventReadyToSend(NodeId node_id, bool& cancel_recv)\n             hdr_bytes_sent + body_bytes_sent,\n             client->m_origin,\n             client->m_node_id);\n\n         // Our work is done here\n         if (!res.m_keep_alive) {\n-            cancel_recv = false;\n+            cancel_recv = true;\n             client->m_disconnect = true;\n             return;\n         }\n     }\n-\n-    // Next attempt to receive data from this node is permitted\n-    cancel_recv = false;\n }\n\n void HTTPServer::EventGotData(NodeId node_id, const uint8_t* data, size_t n)\n {\n     // Get the HTTPClient\n     auto client{WITH_LOCK(m_clients_mutex, return GetClientById(node_id);)};\n```"
  },
  {
   "t": "2025-01-28T20:23:58Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1927267574,
   "text": "awesome thank you"
  },
  {
   "t": "2025-01-28T20:29:53Z",
   "kind": "comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "text": "Something else I see using Sockman is in the `mempool_limit` functional test, we call `getrawtransaction` and expect around 540,000 bytes back. The call to `send()` returns the total amount of bytes immediately, but it requires 60-70 TCP packets to actually transmit the response. I think what I'm seeing in wireshark is that this transmission is incomplete when I close the HTTP connection with `CloseConnection()` which closes the underlying socket. The client waits 120 seconds for the rest of its data which never arrives before timing out. I'm not sure if there's a flag in one of the syscalls or another way to check the kernel's socket buffer before closing?"
  },
  {
   "t": "2025-01-29T16:27:14Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nHmm, what I observed is that the send call sends less bytes than requested.\n\n_(Lets move the discussion away from the main thread of this PR into the below link)_\n\nSome a more elaborate explanation plus patch that fixes the problem in https://github.com/pinheadmz/bitcoin/commit/df1965a4e97ce7f3df067df9b3dcc2bbd8e6e15b#r151906181"
  },
  {
   "t": "2025-01-29T21:01:53Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "`strError` only appears in the comment, and \"error message\" should probably be snake_case:\n```suggestion\n     * @param[out] err_msg Error string if an error occurs.\n     * @retval true Success.\n     * @retval false Failure, `err_msg` will be set.\n     */\n    bool BindAndStartListening(const CService& to, bilingual_str& err_msg);\n```"
  },
  {
   "t": "2025-01-29T21:39:34Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "Do we ever want to remove individual entries from `m_listen_permissions` upon disconnect of single peers, long before `.clear()`?\n\nSame goes for `m_listen`. I'm not claiming `vhListenSocket` was any better before this PR."
  },
  {
   "t": "2025-01-29T21:42:23Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "Seems like `.emplace()` will [not replace existing entries](https://en.cppreference.com/w/cpp/container/unordered_map/emplace). In the off chance that someone takes a peer offline and then restarts it with other permissions, would it not be better to do this?\n```suggestion\n    m_listen_permissions[addr] = permissions;\n```"
  },
  {
   "t": "2025-01-29T21:57:39Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "In fd81820214e695ba228a954506397c3d781fe3fe:\nnit: Possible less mutating simplification (assumes `NetPermissionFlags::None == 0`):\n```diff\n-     NetPermissionFlags permission_flags = NetPermissionFlags::None;\n-     auto it{m_listen_permissions.find(addr_bind)};\n-     if (it != m_listen_permissions.end()) {\n-         NetPermissions::AddFlag(permission_flags, it->second);\n-     }\n+     auto it{m_listen_permissions.find(addr_bind)};\n+     NetPermissionFlags permission_flags = it != m_listen_permissions.end() ?\n+         it->second : NetPermissionFlags::None;\n```"
  },
  {
   "t": "2025-01-29T22:04:48Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "nit: The comment adds no information.\n```suggestion\n    std::vector<std::shared_ptr<Sock>> m_listen;\n```\nSame for `m_unused_i2p_sessions_mutex`."
  },
  {
   "t": "2025-01-29T22:15:01Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "nit: Might have a more precise name since we are releasing references to `shared_ptr`s?\n```suggestion\nvoid SockMan::ReleaseSockets()\n```"
  },
  {
   "t": "2025-01-29T22:20:07Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "nit: Sufficiently critical for a generic error?\n```suggestion\n        LogError(\"%s\", strError.original);\n```\n(Can also omit trailing newline here and in other added/modified log lines)."
  },
  {
   "t": "2025-01-29T22:34:10Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "Problem: \" (<CLIENT_NAME> already running?)\"-`string` is untranslated.\n\nWould suggest bringing back old strings to reduce translation churn, or ensuring the new version is fully translated. Old strings:\n```suggestion\n        if (err == WSAEADDRINUSE) {\n            err_msg = strprintf(_(\"Unable to bind to %s on this computer. %s is probably already running.\"), to.ToStringAddrPort(), CLIENT_NAME);\n        } else {\n            err_msg = strprintf(_(\"Unable to bind to %s on this computer (bind returned error %s)\"), to.ToStringAddrPort(), NetworkErrorString(nErr));\n        }\n```"
  },
  {
   "t": "2025-01-29T22:41:14Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "nit: Curious why you changed these to be categorized (used to be `LogPrintf()` without category) - worth noting in commit message?\n```suggestion\n        LogInfo(\"Cannot set SO_REUSEADDR on %s listen socket: %s, continuing anyway\",\n                to.ToStringAddrPort(),\n                NetworkErrorString(WSAGetLastError()));\n```"
  },
  {
   "t": "2025-01-29T22:42:59Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "nit: listen to -> listen on?\n```suggestion\n        errmsg = strprintf(_(\"Cannot listen on %s: %s\"), to.ToStringAddrPort(), NetworkErrorString(WSAGetLastError()));\n```"
  },
  {
   "t": "2025-01-30T09:18:37Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "nit: Leftover `struct` for instance and C-style casts post-modernization. Also in `BindAndStartListening`.\n\n```suggestion\n    sockaddr_storage sockaddr_bind;\n    socklen_t sockaddr_bind_len = sizeof(sockaddr_bind);\n    if (!sock.GetSockName(reinterpret_cast<sockaddr*>(&sockaddr_bind), &sockaddr_bind_len)) {\n        addr_bind.SetSockAddr(reinterpret_cast<const sockaddr*>(&sockaddr_bind));\n```"
  },
  {
   "t": "2025-01-30T09:35:02Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "Since it's only inherited privately, destructor could be non-`virtual` and `protected` unless we want to destroy by `SockMan`-reference?\n\nHappy to see you made the virtual methods only called by `SockMan` private. :)"
  },
  {
   "t": "2025-01-30T09:43:42Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "nit: Could replace `return` with `else if` below?"
  },
  {
   "t": "2025-01-30T09:58:31Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "nit: One plural word is enough?\n```suggestion\n     * Start the necessary threads for socket IO.\n     */\n    void StartSocketThreads(const Options& options);\n\n    /**\n     * Join (wait for) the threads started by `StartSocketThreads()` to exit.\n     */\n    void JoinSocketThreads();\n```"
  },
  {
   "t": "2025-01-30T10:26:46Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "nit: Could go with something closer to original?\n```C++\n    if (auto it{m_nodes.find(id)}; it != m_nodes.end()) {\n        auto node{it->second};\n        LogDebug(BCLog::NET, \"disconnect by id peer=%d; disconnecting\\n\", id);\n        node->fDisconnect = true;\n        return true;\n    } else {\n        return false;\n    }\n```\nSame pattern of `auto it{m_nodes.find(` appearing on it's own line when it could be hoisted into the `if` to reduce scope is repeated elsewhere too."
  },
  {
   "t": "2025-01-30T10:32:56Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "Seems like a slight behavior change warranting it's own commit or at least a mention in the commit message doing the change?"
  },
  {
   "t": "2025-01-30T10:38:33Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/net_peer_connection_tests.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "nit:\n```suggestion\n    for (const auto& [_, node] : connman->TestNodes()) {\n```"
  },
  {
   "t": "2025-01-30T10:43:22Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/connman.cpp",
   "commit": "8725d10df5050f9ac2a7cbedf2502716786fca84",
   "in_reply_to": null,
   "text": "Why are we now setting the `node_id` so deterministically?"
  },
  {
   "t": "2025-01-30T10:48:34Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/util/net.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "Might want to assert in case a test assigns ids randomly and the birthday paradox occurs on annoyingly rare test-runs.\n```suggestion\n        auto [_, success] = m_nodes.emplace(node.GetId(), &node);\n        Assert(success);\n```"
  },
  {
   "t": "2025-01-30T11:00:02Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "7aca044a932e163445f2fccec7739aa7768cf36e: Why assert this at the beginning of the thread, is it to help linters/sanitizers?"
  },
  {
   "t": "2025-01-30T11:07:10Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "An issue here is that we return a `CNode*`, unlock `m_nodes_mutex`, then another thread comes along and deletes the node - leaving the caller with a dangling pointer.\n\nMaybe there are implementation details that make that impossible right now, but it would feel safer to instead have this function require the caller locks `m_nodes_mutex`, or do an `AddRef()` internally and require caller to do `Release()`. (Switching to `shared_ptr` instead of custom ref-counting might be more straight forward but maybe too disruptive)."
  },
  {
   "t": "2025-01-30T14:01:58Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "7866c736c87a908cef75dc6901c9c0594c65b0eb"
  },
  {
   "t": "2025-01-30T14:02:16Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`266ac32673...7866c736c8`: rebase due to conflicts"
  },
  {
   "t": "2025-01-30T15:19:37Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "nit: Could use `span`,  and maybe even `byte`?\n```suggestion\n    virtual void EventGotData(NodeId node_id, const std::span<std::byte> data) = 0;\n```"
  },
  {
   "t": "2025-01-30T15:25:18Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/net.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "248ec2d2687fae47b63688e00b9ef18d4c0c9676 nit: when I run the linter (along with all other tests) on this commit it complains about trailing whitespace.\n\n```diff\ndiff --git a/src/net.cpp b/src/net.cpp\nindex 8d0dd84d91..0228ddce57 100644\n--- a/src/net.cpp\n+++ b/src/net.cpp\n@@ -2068,7 +2068,7 @@ void CConnman::EventIOLoopCompletedForAllPeers()\n     DisconnectNodes();\n     NotifyNumConnectionsChanged();\n }\n-\n+\n Sock::EventsPerSock CConnman::GenerateWaitSockets(Span<CNode* const> nodes)\n {\n     AssertLockNotHeld(m_nodes_mutex);\ndiff --git a/src/net.h b/src/net.h\nindex 8778bcb88a..6c2c202fbb 100644\n--- a/src/net.h\n+++ b/src/net.h\n@@ -1317,7 +1317,7 @@ private:\n\n     virtual void EventIOLoopCompletedForAllPeers() override\n         EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex, !m_reconnections_mutex);\n-\n+\n     /**\n      * Generate a collection of sockets to check for IO readiness.\n      * @param[in] nodes Select from these nodes' sockets.\n```"
  },
  {
   "t": "2025-01-30T15:42:49Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "nit:\n```suggestion\n    if (auto addr_to{std::get_if<CService>(&to)}) {\n```"
  },
  {
   "t": "2025-01-31T09:01:10Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "f7dd4373b06daf34033fa84fa99408a3938e4ad4: would it make sense to call `MarkAsDisconnectAndCloseConnection` in the connman fuzzer?"
  },
  {
   "t": "2025-01-31T11:18:34Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "In commit 0be688360318511ecebcfc9cbabacfa6960fa5ef:\nThe new code has the `ThreadI2PAccept()` -> `NewSockAccepted()` -> `EventNewConnectionAccepted()` which now contains:\n```\n    const CService addr_bind{MaybeFlipIPv6toCJDNS(addr_bind_)};\n    const CService addr{MaybeFlipIPv6toCJDNS(addr_)};\n```\nIs it okay to combine I2P with CJDNS, is this a bugfix?"
  },
  {
   "t": "2025-01-31T11:49:37Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "f7dd4373b06daf34033fa84fa99408a3938e4ad4: why plural and not `NodeSocket`?\n\nDoes the I2P session `sess` also contain a socket? Or does it use `s`?"
  },
  {
   "t": "2025-01-31T13:14:26Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "f7dd4373b06daf34033fa84fa99408a3938e4ad4: it would be useful to document these two values."
  },
  {
   "t": "2025-01-31T14:21:08Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "f7dd4373b06daf34033fa84fa99408a3938e4ad4: maybe add a comment here that both `-1` and `0` still warrant (require?) reaching `EventIOLoopCompletedForNode`, so we can't just have individual `if ... continue` guards for them."
  },
  {
   "t": "2025-01-31T14:24:13Z",
   "kind": "review",
   "who": "Sjors",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "7866c736c87a908cef75dc6901c9c0594c65b0eb",
   "text": "Reviewing in reverse order.\n\nf7dd4373b06daf34033fa84fa99408a3938e4ad4 is a beast, but looks correct. I'm not sure if it can be split further. There's some mixing of code modernisation with the move which make the diff a bit trickier to follow."
  },
  {
   "t": "2025-01-31T20:14:40Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "Forgot to remove `CNode::m_sock_mutex`."
  },
  {
   "t": "2025-01-31T20:18:35Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "nit: Much whitespace\n```suggestion\n        std::string errmsg;\n        const ssize_t sent{SendBytes(node.GetId(), data, more, errmsg)};\n        if (sent > 0) {\n```"
  },
  {
   "t": "2025-02-01T14:57:00Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "nit: Don't you want to rename these arguments to the new interface names from `SockMan` and *net.h*?"
  },
  {
   "t": "2025-02-01T15:46:50Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "Before looking at this PR, I was playing around with the idea of changing the raw access of `CNode::fDisconnect` everywhere into two functions, `QueueDisconnect` and `DisconnectNow`. One would just set the flag and the other would also release the socket reference. My idea was to have both of them require a `reason`-argument and an optional `BCLog::Level`-argument. They would log internally if `fDisconnect` was not already set, instead of having every call site log. `MarkAsDisconnectAndCloseConnection`, seems like an opportunity to do such a thing.\n\n[WIP disconnecting++ branch](https://github.com/bitcoin/bitcoin/compare/master...hodlinator:bitcoin:2025/01/disconnecting++)\n\nTotally fine with considering it out of scope for this PR.\n```suggestion\n                MarkAsDisconnectAndCloseConnection(node, \"socket send error: %s\", err_msg);\n```"
  },
  {
   "t": "2025-02-01T20:18:34Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "Worth double-checking in case it doesn't hold for all third-party `recv()`-implementations?\n```suggestion\n            default:\n                Assert(nrecv > 0); // Should have caught all negative values above.\n                EventGotData(node_id, buf, nrecv);\n                break;\n```"
  },
  {
   "t": "2025-02-01T20:37:50Z",
   "kind": "review",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "state": "CHANGES_REQUESTED",
   "commit": "7866c736c87a908cef75dc6901c9c0594c65b0eb",
   "text": "Concept ACK 46131d44faeb797909e0fc3a2042492adef9aa0d\n\n(Still plan to check some in-depth aspects).\n\nNice to separate out some concerns!\n\nRelieved to see sockets actually moved down into `Sockman` in next-to-last commit. :)\n\n#### WinSock\n\nWould be nice to have socket/WinSock code in fewer files. Current status:\n```\n\u20bf git grep -c -E \"\\bWSA\"\nsrc/common/pcp.cpp:10\nsrc/common/sockman.cpp:13\nsrc/common/system.cpp:2\nsrc/compat/compat.h:12\nsrc/net.cpp:1\nsrc/netbase.cpp:11\nsrc/util/sock.cpp:5\n```\nCould at least remove the remaining call in *net.cpp* by moving `CNetCleanup` to *sockman.cpp*?\n\n#### Terminology\n\nnit: I prefer \"local\"/\"remote\" or \"self\"/\"other\" over \"me\"/\"them\". `i2p::Connection` uses \"me\"/\"peer\" though.\n\n#### 7866c736c87a908cef75dc6901c9c0594c65b0eb / net: move-only: improve encapsulation of SockMan\n\nCommit message typo: `m_listen private`\n\n#### 645f83354c0304fd339a45d9439084a44b7ca415 / net: reduce CAddress usage to CService or CNetAddr\n\nAre we going to change file/network serialization by switching `CAddress` -> `CService`? Might try to answer this in later review."
  },
  {
   "t": "2025-02-03T09:19:44Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1934722181,
   "text": "It's easier to read in doxygen html, and can always be expanded."
  },
  {
   "t": "2025-02-03T09:30:21Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1934731306,
   "text": "IIUC the difference between release and close is only relevant in edge cases. The main purpose of this function it to close them. I would keep the name, but maybe document the function to point out the edge case."
  },
  {
   "t": "2025-02-03T09:34:24Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1934736289,
   "text": "According to developer notes, `LogError` is for:\n\n[quoted text omitted]\n  entirely\n\nAnd IIUC we indeed shut down here."
  },
  {
   "t": "2025-02-03T13:22:17Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1937072454,
   "text": "`MaybeFlipIPv6toCJDNS` only does something if `addr_(bind_)` `IsIPv6()`, which checks `m_net == NET_IPV6`.\n\nThis is the default for a `CNetAddr`, however deeply buried in `ThreadI2PAccept` - > `i2p::sam::Session::Accept` -> `DestB64ToAddr` -> `DestBinToAddr` -> `CNetAddr::SetSpecial` -> `SetI2P`  do we find `m_net = NET_I2P`. So the flip won't interfere with an I2P connection (in the case its prefix coincidentally matches the IPv6 CJDNS prefix).\n\nBut maybe you should add a `Assume(IsI2P(conn.peer))`;\n\n(in 0be688360318511ecebcfc9cbabacfa6960fa5ef \"net: tweak EventNewConnectionAccepted()\")"
  },
  {
   "t": "2025-02-03T13:41:29Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "In 91d97a198fb57d47870ce094454c3776bc737ba0 \"net: split CConnman::ConnectNode()\": why this new `assert`? If you need it all, maybe make it an `Assume`?"
  },
  {
   "t": "2025-02-03T13:49:55Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "In 91d97a198fb57d47870ce094454c3776bc737ba0 \"net: split CConnman::ConnectNode()\": looking at the body of `ConnectAndMakeNodeId` the code paths with and without proxy are vastly different. They also have separate call sites.\n\nSo instead of using an `std::variant`, it seems more clear to just have both `ConnectAndMakeNodeId` and `ProxyConnectAndMakeNodeId`."
  },
  {
   "t": "2025-02-05T16:26:06Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Merge conflict is probably from #25832."
  },
  {
   "t": "2025-02-06T13:24:45Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1804940161,
   "text": "Changed. This is now `SockMan::Id` and in `net.h`: `using NodeId = SockMan::Id;`. In SockMan comments change the word \"node\" to \"connection\". This caused a lot of mechanical renames within this PR, but it looks better now, thanks!\n\n`SockMan::GetNewNodeId()` renamed to\n`SockMan::GetNewId()`\n\n`SockMan::EventIOLoopCompletedForNode()` renamed to\n`SockMan::EventIOLoopCompletedForOne()`\n\n`SockMan::EventIOLoopCompletedForAllPeers()` renamed to\n`SockMan::EventIOLoopCompletedForAll()`\n\n`SockMan::ConnectAndMakeNodeId()` renamed to\n`SockMan::ConnectAndMakeId()`.\n\nWill be in next push."
  },
  {
   "t": "2025-02-06T14:23:44Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1916636569,
   "text": "Changed to `EventI2PStatus()` and instead of `bool` it now takes an enum with (currently) two possible values: `START_LISTENING` and `STOP_LISTENING`. Will be in next push."
  },
  {
   "t": "2025-02-06T14:30:31Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1935808313,
   "text": "Fixed."
  },
  {
   "t": "2025-02-06T14:56:57Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1936898358,
   "text": "Yes, done."
  },
  {
   "t": "2025-02-06T14:58:32Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "I think libevent might actually use `45` for the `poll()` loop:\n\nhttps://github.com/libevent/libevent/blob/112421c8fa4840acd73502f2ab6a674fc025de37/http-internal.h#L17-L20\n\nWhat's the best way to determine this constant? Sockman-based HTTP certainly gets through the functional test suite a lot faster when this value is reduced (I tried `10` here)"
  },
  {
   "t": "2025-02-06T15:00:51Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1937107332,
   "text": "Yes, the I2P session contains a socket:\n\nhttps://github.com/bitcoin/bitcoin/blob/d6c229d8bd4a6203a7255c140aa35c59fb20378b/src/i2p.h#L251-L260"
  },
  {
   "t": "2025-02-06T15:08:52Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1937302384,
   "text": "Done."
  },
  {
   "t": "2025-02-06T15:13:38Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1937394233,
   "text": "Done."
  },
  {
   "t": "2025-02-06T17:07:13Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "if `WaitMany()` returns false, we would have already waited the duration of `SELECT_TIMEOUT` -- do we need to `sleep_for()` again?"
  },
  {
   "t": "2025-02-06T19:19:57Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1945120612,
   "text": "For example I tried this. I expected a performance boost but didn't really observe one:\n\n```cpp\n\n        auto io_readiness{GenerateWaitSockets()};\n        if (io_readiness.events_per_sock.empty()) {\n            interruptNet.sleep_for(SELECT_TIMEOUT);\n        } else {\n            // WaitMany() may as well be a static method, the context of the first Sock in the vector is not relevant.\n            io_readiness.events_per_sock.begin()->first->WaitMany(SELECT_TIMEOUT,\n                                                                   io_readiness.events_per_sock);\n        }\n```"
  },
  {
   "t": "2025-02-07T05:26:15Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1934587454,
   "text": "Done."
  },
  {
   "t": "2025-02-07T05:33:18Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1934686592,
   "text": "No, we do not want to remove entries from `m_listen_permissions` or from `m_listen` when disconnecting a single peer. The entries in those two maps are per listening address. E.g. if we listen on `1.1.1.1:8333` and designate that every peer that connects to that address gets `permissions1` and listen on `2.2.2.2:8333` and peers that connect to that address get `permissions2`. This stays unchanged as peers connect and disconnect. We would want to remove entries from those maps if we stop listening - i.e. if we close the listening socket. This is only done at shutdown."
  },
  {
   "t": "2025-02-07T05:44:41Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1934690968,
   "text": "`addr` is our listening address. We can't listen on the same address two times:\n\nIf I put this in my `bitcoind.conf`:\n```\nbind=127.0.0.1:20001\nbind=127.0.0.1:20002\nbind=127.0.0.1:20003\nbind=127.0.0.1:20003\n```\n\nI get:\n\n```\n2025-02-07T05:39:56Z [net:info] Bound to and listening on 127.0.0.1:20001\n2025-02-07T05:39:56Z [net:info] Bound to and listening on 127.0.0.1:20002\n2025-02-07T05:39:56Z [net:info] Bound to and listening on 127.0.0.1:20003\n2025-02-07T05:39:56Z [net:error] Cannot bind to 127.0.0.1:20003: Address already in use (48) (Bitcoin Core already running?)\n```\nand startup is aborted.\n\nI could add `Assume()` or return an error from here if insertion fails due to an already existent entry?"
  },
  {
   "t": "2025-02-07T05:53:49Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1934710097,
   "text": "The proposed snippet is more elegant and performant, I like it. It relies, however, on `None` being `0` and will break in a subtle way if that is changed. I will leave it as it is. This is executed once per newly accepted connection which is not super-often and the performance gain would be negligible."
  },
  {
   "t": "2025-02-07T05:56:04Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1934722181,
   "text": "Agree, will leave it as it is."
  },
  {
   "t": "2025-02-07T06:07:38Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1934731306,
   "text": "This method was only dealing with listening sockets, so the name `CloseSockets()` with a comment \"Close all sockets\" was confusing. Renamed to `StopListening()`."
  },
  {
   "t": "2025-02-07T09:26:38Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1934736289,
   "text": "Changed to `LogError()` and removed the newline. I did not know the trailing newlines are now unnecessary (since bbbb2e43ee95c9a8866aa1f65e3f001f752dfed2)."
  },
  {
   "t": "2025-02-07T09:35:15Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1934748506,
   "text": "Done."
  },
  {
   "t": "2025-02-07T09:38:33Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1934754380,
   "text": "Done."
  },
  {
   "t": "2025-02-07T09:53:55Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1934686592,
   "text": ":facepalm: makes total sense, I was thinking about the remote sockets."
  },
  {
   "t": "2025-02-07T09:55:36Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1934690968,
   "text": "Yeah, sorry, me confusing sockets again. An `Assume()` would be nice though."
  },
  {
   "t": "2025-02-07T09:58:21Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1934755815,
   "text": "Done."
  },
  {
   "t": "2025-02-07T10:08:11Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1935267717,
   "text": "Removed `struct` from `BindAndStartListening()` in commit `style: modernize the style of SockMan::BindListenPort()`.\n\n`GetBindAddress()` was just moved verbatim without any mods from `net.cpp` to `sockman.cpp` in another commit. This makes it easier to review with\n```\n[diff]\n        colorMoved = dimmed-zebra\n        colorMovedWS = allow-indentation-change\n```\nin `~/.gitconfig`. Will leave it as it is. Don't want to bloat this PR further with one more \"style changing\" commit."
  },
  {
   "t": "2025-02-07T10:18:35Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1935304394,
   "text": "In general, less indentation makes the code more readable, so I prefer to have `return;` and then the subsequent code to be with less indentation.\n\nAnyway this code was changed and now uses `switch` (see `CConnman::EventI2PStatus()` after I push)."
  },
  {
   "t": "2025-02-07T10:22:11Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935292076,
   "text": "```\nerror: delete called on non-final 'CConnman' that has virtual functions but non-virtual destructor [-Werror,-Wdelete-non-abstract-non-virtual-dtor]\n```"
  },
  {
   "t": "2025-02-07T10:28:14Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935326693,
   "text": "There are multiple sockets and multiple threads. \"socket threads\" looks like there is one socket and multiple threads. Will leave it as it is."
  },
  {
   "t": "2025-02-07T10:34:47Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935367810,
   "text": "Less indentation makes the code more readable in general. So I prefer:\n\n```\nif (A) {\n    return;\n}\nCODE;\n```\nover:\n```\nif (!A) {\n    CODE;\n} else {\n    return;\n}\n```\n\nReduced scope is nice, but I find `if (auto it{m_nodes.find(id)}; it != m_nodes.end()) {` hard to read. Sometimes I confuse it for a for-loop: `for (foo; bar; baz) {`. Also in this particular case the function ends right after this, so the scope of `it` is the same.\n\nWill leave it as it is."
  },
  {
   "t": "2025-02-07T10:53:36Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935376623,
   "text": "There should be no change in behavior. Note that the original code in `master` was very subtle - it required that `ForEachNode()` would iterate the nodes in increasing order of `id`:\n\n```cpp\nstd::pair<NodeId, std::chrono::seconds> youngest_peer{-1, 0}, next_youngest_peer{-1, 0};\n\nm_connman.ForEachNode([&](CNode* pnode) {\n    if (!pnode->IsBlockOnlyConn() || pnode->fDisconnect) return;\n    if (pnode->GetId() > youngest_peer.first) {\n        next_youngest_peer = youngest_peer;\n        youngest_peer.first = pnode->GetId();\n        youngest_peer.second = pnode->m_last_block_time;\n    }\n});\n```\nThe condition `pnode->GetId() > youngest_peer.first` would be `true` every time. Because of that, this code is equivalent to:\n\n```cpp\nstd::pair<NodeId, std::chrono::seconds> youngest_peer{-1, 0}, next_youngest_peer{-1, 0};\n\nm_connman.ForEachNode([&](CNode* pnode) {\n    if (!pnode->IsBlockOnlyConn() || pnode->fDisconnect) return;\n    next_youngest_peer = youngest_peer;\n    youngest_peer.first = pnode->GetId();\n    youngest_peer.second = pnode->m_last_block_time;\n});\n```\n\nThe new code will again select the two youngest peers (the two with the bigger `id`s), but does not require that the iterating is in increasing order of `id`:\n\n```cpp\nstd::pair<NodeId, std::chrono::seconds> youngest_peer{-1, 0}, next_youngest_peer{-1, 0};\n\nm_connman.ForEachNode([&](CNode* pnode) {\n    if (!pnode->IsBlockOnlyConn() || pnode->fDisconnect) return;\n    if (pnode->GetId() > next_youngest_peer.first) {\n        if (pnode->GetId() > youngest_peer.first) {\n            next_youngest_peer = youngest_peer;\n            youngest_peer.first = pnode->GetId();\n            youngest_peer.second = pnode->m_last_block_time;\n        } else {\n            next_youngest_peer.first = pnode->GetId();\n            next_youngest_peer.second = pnode->m_last_block_time;\n        }\n    }\n});\n```"
  },
  {
   "t": "2025-02-07T10:55:25Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/net_peer_connection_tests.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1935384176,
   "text": "Done."
  },
  {
   "t": "2025-02-07T11:00:05Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/connman.cpp",
   "commit": "8725d10df5050f9ac2a7cbedf2502716786fca84",
   "in_reply_to": 1935390847,
   "text": "To avoid overlaps. In `master`, without this PR, this test could generate nodes with duplicate `id`s."
  },
  {
   "t": "2025-02-07T11:05:10Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/util/net.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1935397798,
   "text": "Done."
  },
  {
   "t": "2025-02-07T11:12:08Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935292076,
   "text": "Ah, and we can't make `CConnman` `final` while we have `struct ConnmanTestMsg : public CConnman`, got it. Should have tested before suggesting."
  },
  {
   "t": "2025-02-07T11:13:05Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935414710,
   "text": "It is for every function, not just \"thread\" ones - `doc/developer-notes.md` says:\n\n[quoted text omitted]\nThis means:\n\n```cpp\nvoid f() EXCLUSIVE_LOCKS_REQUIRED(!m);\n\n...\n\nvoid f()\n{\n    AssertLockNotHeld(m);\n    ...\n}\n```\n\nThe reasoning is that the annotation `EXCLUSIVE_LOCKS_REQUIRED` can be ignored. It is nice that the check is done at compile time, but if the compiler is not clang then it will be ignored, or if the compiler is clang but is running without `-Werror`, then it will only result in a compile warning which is likely to be missed in the build output. In other words, it is possible to create a violating code and compile and run it with both gcc and clang. In that case `AssertLockNotHeld()` will stop the program at runtime."
  },
  {
   "t": "2025-02-07T11:15:13Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935326693,
   "text": "`SocketsAndThreads` would be clearer, but I think the current name is fine."
  },
  {
   "t": "2025-02-07T11:20:08Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935326693,
   "text": "We open/close sockets, we don't start/join them, so I prefer the current name over adding \"And\"."
  },
  {
   "t": "2025-02-07T11:23:55Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935414710,
   "text": "Ah, so it's done as a general rule even if it makes less logical sense for the function at the root of a thread (as nothing can have taken the lock before it unless the thread is wrapped in some kind of utility that is weird enough to take locks)."
  },
  {
   "t": "2025-02-07T12:19:21Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935376623,
   "text": "Ah, that was subtle, thanks for laying it out so clearly.\n\nMaybe amend the commit message in \"net: index nodes in CConnman by id\" with something like:\n[quoted text omitted]"
  },
  {
   "t": "2025-02-07T13:12:28Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935423919,
   "text": "Maybe it is worth doing this: https://github.com/bitcoin/bitcoin/pull/30988#issuecomment-2485715952. It is a super good change and normally I would do it, but the reason I didn't is that it will increase the size of this PR which, I am afraid, would turn reviewers away.\n\nI will proceed to other suggestions and give this some thought..."
  },
  {
   "t": "2025-02-07T13:24:35Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1935798322,
   "text": "Done, but used `uint8_t` because `ReceiveMsgBytes()` (in `master`) takes a span of that and conversion is not possible."
  },
  {
   "t": "2025-02-07T13:30:08Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1935798322,
   "text": "[quoted text omitted]\n\nIt should be possible to convert std::byte* to uint8_t* (and vice-versa). The two are the almost the same anyway (https://en.cppreference.com/w/cpp/types/byte). And a span is just a pointer+size, so conversion between the two span types should also be possible."
  },
  {
   "t": "2025-02-07T13:37:52Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935839489,
   "text": "That would make `addr_to` a pointer, so `*addr_to` would have to be used in the 6 places below. I slightly prefer the current \"if holds alternative CService\" because it is dump and easy to follow. Leaving it as it is."
  },
  {
   "t": "2025-02-07T13:42:48Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1937072454,
   "text": "Yeah, `EventNewConnectionAccepted()` is generic used also for non-I2P connections and the flip will not do anything on I2P addresses.\n\n[quoted text omitted]\nIn which function?"
  },
  {
   "t": "2025-02-07T13:45:22Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1937937184,
   "text": "Removed!"
  },
  {
   "t": "2025-02-07T13:47:26Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1937940500,
   "text": "Done."
  },
  {
   "t": "2025-02-07T13:51:24Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1937072454,
   "text": "I think in `ThreadI2PAccept()` right before it calls `NewSockAccepted()`"
  },
  {
   "t": "2025-02-07T13:56:04Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1938288697,
   "text": "Done."
  },
  {
   "t": "2025-02-07T14:08:32Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1938295568,
   "text": "Hmm, is not straight forward. From all callers of `MarkAsDisconnectAndCloseConnection()`:\n\n* 3 unconditional `LogDebug(BCLog::NET`\n* 1 no log\n* 2 conditional `LogDebug(BCLog::NET` based on `fDisconnect`\n\nSo it is not like all the callers do the same thing. Maybe it is ok to consolidate those into one behavior, but would be a functional change that is not the purpose of this PR. Leaving it as it is, but sounds like something to explore as a followup."
  },
  {
   "t": "2025-02-07T14:17:41Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1938338721,
   "text": "Hmm, if we are worried that `recv(2)` will return a value less than `-1`, then we would better treat that as an error instead of terminating the whole program. Changed the `switch` to `if/else`, treating all negative values as an error."
  },
  {
   "t": "2025-02-07T14:49:00Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1939396960,
   "text": "Because later on in `ConnectAndMakeId()` there is logic that will be confused if the default constructed proxy is valid. But it is better to use a `std::optional` to designate \"no proxy\" instead of default-contructed-and-invalid proxy. Then this assert/Assume is not necessary. Changed."
  },
  {
   "t": "2025-02-07T14:54:05Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1939408935,
   "text": "But there is still some common code that would have to be duplicated:\n\n```cpp\n{\n    AssertLockNotHeld(m_connected_mutex);\n    AssertLockNotHeld(m_unused_i2p_sessions_mutex);\n\n    std::unique_ptr<Sock> sock;\n    std::unique_ptr<i2p::sam::Session> i2p_transient_session;\n\n    Assume(!me.IsValid());\n\n    if (std::holds_alternative<CService>(to)) {\n        ...\n    } else {\n        ...\n    }\n\n    if (!sock) {\n        return std::nullopt;\n    }\n\n    if (!me.IsValid()) {\n        me = GetBindAddress(*sock);\n    }\n\n    const Id id{GetNewId()};\n\n    {\n        LOCK(m_connected_mutex);\n        m_connected.emplace(id, std::make_shared<ConnectionSockets>(std::move(sock),\n                                                                    std::move(i2p_transient_session)));\n    }\n\n    return id;\n}\n```"
  },
  {
   "t": "2025-02-07T15:02:24Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1939408935,
   "text": "Everything starting at `if (!sock)` could go into a helper function? FinishConnectStuff()"
  },
  {
   "t": "2025-02-07T15:10:18Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935292076,
   "text": "(Could still make the `SockMan` destructor `protected` and non-`virtual` if the `CConnman` destructor was made `virtual`, but there is little to gain from it)."
  },
  {
   "t": "2025-02-07T15:41:48Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1944879162,
   "text": "I just copied this from `master`:\n\n```cpp\nstatic const uint64_t SELECT_TIMEOUT_MILLISECONDS = 50;\n```\n\nI guess some empirical testing... This constant is used in two places: in `WaitMany()` and in the sleep if no sockets at all, maybe those two should be separate constants (btw the same is in `master`)."
  },
  {
   "t": "2025-02-07T16:06:44Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1945120612,
   "text": "[quoted text omitted]\n\nNo, `false` means an error, e.g. `poll(2)` returning `-1`. A timeout is signaled by a return value of `true` and all `what[].occurred` returned as `0`."
  },
  {
   "t": "2025-02-07T19:25:32Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "7d84f431f9fd89b647f385ea5224b5ad76236844"
  },
  {
   "t": "2025-02-07T19:26:45Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`7866c736c8...7d84f431f9`: rebase due to conflicts and address suggestions\n\nInterface changes:\n\n`SockMan::CloseSockets()` renamed to\n`SockMan::StopListening()`\n\n`SockMan::GetNewNodeId()` renamed to\n`SockMan::GetNewId()`\n\n`NodeId` renamed to\n`SockMan::Id`\n\n`SockMan::EventI2PListen()` renamed to\n`SockMan::EventI2PStatus()` and instead of `bool` it now takes an `enum I2PStatus` argument\n\n`SockMan::EventIOLoopCompletedForNode()` renamed to\n`SockMan::EventIOLoopCompletedForOne()`\n\n`SockMan::EventIOLoopCompletedForAllPeers()` renamed to\n`SockMan::EventIOLoopCompletedForAll()`\n\n`EventGotData()` now takes `std::span` instead of pointer+length\n\n`SockMan::ConnectAndMakeNodeId()` renamed to\n`SockMan::ConnectAndMakeId()` and takes `std::optional<Proxy>` instead of `Proxy` to more clearly denote the \"no proxy given\" case (empty optional, was an invalid proxy before).\n\nprivate:\n\n`SockMan::NodeSockets` renamed to\n`SockMan::ConnectionSockets`\n\n`SockMan::GetNodeSockets()` renamed to\n`SockMan::GetConnectionSockets()`"
  },
  {
   "t": "2025-02-10T09:12:25Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "It is better to post such suggestions not in the main thread of the PR but as a comment to some line of code, even if that is a random, unrelated line. That way replies will be grouped together with the questions instead of being scattered around in the main PR thread. In the main thread it is easier to forget to reply to some questions. As comments to some line of code, they can be tracked and eventually \"resolved\" to collapse them and reduce the main PR thread noise.\n\n[quoted text omitted]\nI agree, but that is out of the scope of this PR, even `CNetCleanup` is untouched by this PR, so I will leave it as it is.\n\n[quoted text omitted]\nNo strong opinion.\n\n[quoted text omitted]\nFixed.\n\n[quoted text omitted]\nNo."
  },
  {
   "t": "2025-02-10T09:20:47Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935376623,
   "text": "Done, will be in next push."
  },
  {
   "t": "2025-02-10T09:35:46Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1935798322,
   "text": "By default trying to pass `std::span<std::byte>` to a function that takes `std::span<uint8_t>` gives:\n\n```\nno known conversion from 'span<std::byte>' to 'span<uint8_t>'\n```\n\nIf I really insist then I can convert it like:\n\n```cpp\nvoid f(std::span<uint8_t> s);\n...\nstd::span<std::byte> a;\nf(std::span<uint8_t>{reinterpret_cast<uint8_t*>(a.data()), a.size()});\n```\n\nI find it cleaner to use `uint8_t` because it is already used in the existent code (`ReceiveMsgBytes()`) and avoid such forced coversion."
  },
  {
   "t": "2025-02-10T10:20:05Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1935798322,
   "text": "Certainly won't insist, but another possibility is to use `bit_cast` for trivially constructible types:\n```c++\nvoid f(std::span<uint8_t> s);\n...\nstd::span<std::byte> a;\nf(std::bit_cast<std::span<uint8_t>>(a));\n```"
  },
  {
   "t": "2025-02-10T10:40:45Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1935798322,
   "text": "[quoted text omitted]\n\nWhile this will likely work in practise, I don't think there is any inherent guarantee that the layout of span is identical for all underlying types. So my recommendation would be to just use `std::as_bytes` instead.\n\n(Same for the reinterpret_cast: While it works, it is a bit verbose and `std::as_bytes` is the existing alias in the std lib, which is already used in the code today.)\n\nObviously, anything is fine. I just left a comment to say it is possible to convert :sweat_smile:"
  },
  {
   "t": "2025-02-10T10:50:51Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1935798322,
   "text": "Will leave it as `uint8_t` because it is already used in `master` in `ReceiveMsgBytes()` to which we have to pass that variable."
  },
  {
   "t": "2025-02-10T11:07:59Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/connman.cpp",
   "commit": "8725d10df5050f9ac2a7cbedf2502716786fca84",
   "in_reply_to": 1935390847,
   "text": "(Might be worth noting that in commit message for the change that fixes the bug, 60f6cbb9b9f83e25217d30c889147ad517960ec7 / \"net: index nodes in CConnman by id\")."
  },
  {
   "t": "2025-02-10T11:19:36Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1934587454,
   "text": "Thanks! (PR summary is out of date, but I understand if you don't want to constantly manually update it)."
  },
  {
   "t": "2025-02-10T11:25:27Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1939408935,
   "text": "I am considering this, but just realized that the distinction is not \"proxy vs no-proxy\" because in both cases of the `std::variant` we may end up connecting via the proxy. The logic (as convoluted as it is, it is the same in `master`) is:\n\n* if connecting to `CService`\n  * if to an I2P `CService` then proxy must be used\n  * otherwise the proxy is optional, if provided it will be used\n* if connecting to string host, then proxy must be used\n\nSo, if `std::variant` is to be avoided and two functions provided instead of one, then they should be called something like `ConnectToCService()` and `ConnectToString()` :-|\n\nEdit: or a bunch of functions covering each case:\n```cpp\nConnectToI2P(CService, proxy); // proxy must be used\nConnectDirectly(CService); // no proxy\nConnectViaProxy(CService, proxy);\nConnectViaProxy(string, proxy); // overload\n```"
  },
  {
   "t": "2025-02-10T11:42:23Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/connman.cpp",
   "commit": "8725d10df5050f9ac2a7cbedf2502716786fca84",
   "in_reply_to": 1935390847,
   "text": "Done."
  },
  {
   "t": "2025-02-10T11:43:17Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "7b63b4ca1c4bc82bb1673313b326888cdb7eda9b"
  },
  {
   "t": "2025-02-10T11:59:50Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`7d84f431f9...7b63b4ca1c`: minor repush to elaborate a commit message as suggested.\n\nAre you in the mood of reviewing a change to this PR, https://github.com/bitcoin/bitcoin/pull/30988#issuecomment-2485715952, that stores the \"client\" objects in `SockMan` (as `shared_ptr`, not as raw pointers)? In the case of `CConnman` that is `CNode`. This will:\n* make the interaction between `SockMan` and `CConnman` simpler\n* probably faster (less lookups by id)\n* resolve the issue in https://github.com/bitcoin/bitcoin/pull/30988#discussion_r1935423919\n* resolve the issue which https://github.com/bitcoin/bitcoin/pull/28222 tried to resolve\n* will get rid of the manual `CNode` reference counting (`CNode::AddRef()`).\n\nOr I could find a smaller workaround to https://github.com/bitcoin/bitcoin/pull/30988#discussion_r1935423919."
  },
  {
   "t": "2025-02-10T12:47:21Z",
   "kind": "comment",
   "who": "hodlinator",
   "assoc": "CONTRIBUTOR",
   "text": "Would really like to get rid of the custom ref-counting of `CNode` and avoid id-lookups. But fear it will grow this PR a bit much.\n\nWhy not remove the custom ref-counting as a separate PR to begin with? Things like https://github.com/bitcoin/bitcoin/pull/28222#discussion_r1288400379 make me think it may not be trivial, but maybe you have a more elegant approach.\n\nI personally commit to review regardless of whether you split it out to it's own PR."
  },
  {
   "t": "2025-02-13T10:16:39Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1937072454,
   "text": "Added `Assume(IsI2P(conn.peer))` and `Assume(IsI2P(conn.me))` in `ThreadI2PAccept()`."
  },
  {
   "t": "2025-02-13T10:41:41Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "c4ab7f82d6a4265fb96159b6c973ad849df4f8b9"
  },
  {
   "t": "2025-02-13T10:42:09Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`7b63b4ca1c...c4ab7f82d6`: rebase due to conflicts and address a suggestion"
  },
  {
   "t": "2025-02-13T10:52:02Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "Extracted the first commit into https://github.com/bitcoin/bitcoin/pull/31854. It is not strictly related to this PR and makes sense on its own. If merged will reduce the size of this PR."
  },
  {
   "t": "2025-02-13T16:09:26Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Can you update the PR description to say that this is based on #31854? The convention then is to mark this PR draft until that one is merged.\n\nAdditionally, it's 25% easier to review #31854 if you rebase this PR on the exact same commit. Since then I can just range-diff it with what I already reviewed here."
  },
  {
   "t": "2025-02-14T08:37:14Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "563afdd975771cd2b87c4a96e0059f1c6f8774ad"
  },
  {
   "t": "2025-02-14T08:54:56Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`c4ab7f82d6...563afdd975`: pick change from https://github.com/bitcoin/bitcoin/pull/31854 into the first commit of this PR\n\n[quoted text omitted]\nAdded a note at the bottom of the description of this PR. This PR was not a draft before #31854. The creation of #31854 did not change anything about this PR, so I think the existence of a chop off PR shouldn't render this one as a draft. There is no dependency between the two - either one can be merged first.\n\n[quoted text omitted]\nDone."
  },
  {
   "t": "2025-02-14T11:12:28Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": null,
   "text": "Since `key_type` here is `std::shared_ptr<const Sock>` shouldn't this map also use the optimized hash/equal functions defined in:\n\nhttps://github.com/bitcoin/bitcoin/blob/86528937e5c4da2e12c46085fc41e87ed759258e/src/util/sock.h#L208"
  },
  {
   "t": "2025-02-14T12:17:47Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "e1671ff42c385f611af778380a83e98920cee82e"
  },
  {
   "t": "2025-02-14T12:18:07Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`563afdd975...e1671ff42c`: do https://github.com/bitcoin/bitcoin/pull/30988#discussion_r1955976383"
  },
  {
   "t": "2025-02-14T12:18:18Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1955976383,
   "text": "Yes, done!"
  },
  {
   "t": "2025-02-17T08:52:04Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "741f17e51ddac002fadbc735e2ca650117594a3c"
  },
  {
   "t": "2025-02-17T08:52:43Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`e1671ff42c...741f17e51d`: rebase and remove the first commit which was merged via https://github.com/bitcoin/bitcoin/pull/31854, thanks!"
  },
  {
   "t": "2025-02-17T18:44:34Z",
   "kind": "review",
   "who": "jonatack",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "741f17e51ddac002fadbc735e2ca650117594a3c",
   "text": "Concept ACK"
  },
  {
   "t": "2025-02-18T05:44:27Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1935423919,
   "text": "After thinking about this a bit more and doing some changes to the code, I decided to:\n\n1. Open a separate PR to remove the manual ref counting on `CNode` and replace it with `shared_ptr` and change `CConnman::m_nodes` from `vector<CNode*>` to `unordered_set<shared_ptr<CNode>>`. That PR would be independent from this one.\n\n2. In this PR, change the communication between `SockMan` and `CConman` to be based on pointer to `CNode` instead of node id. Similarly to https://github.com/bitcoin/bitcoin/pull/30988#issuecomment-2485715952. I will start with a raw pointer and the responsibility of destruction will be in `CConnman`, like it is now. If 1. is merged in the mean time before this PR, then I will just change this PR to use `shared_ptr` instead of a raw pointer."
  },
  {
   "t": "2025-03-03T18:10:40Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "I may have a situation in HTTP where SendBytes() is being called from a thread other than the Sockman I/O loop, which would be an \"optimistic send\" directly from a worker thread. `CConnman::PushMessage()` has similar logic, but since p2p doesn't use worker threads I don't think it would cause an issue there. I think this assertion may be the only real conflict for that. `m_connected_mutex` is only used in `GetConnectionSockets()` -- would a little lock-waiting there be so terrible?"
  },
  {
   "t": "2025-03-05T09:26:07Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1977960657,
   "text": "Hmm, the assertion is that the mutex is **not** held by the caller of `SendBytes()`. The mutex is private in `SockMan` so it cannot be held by the HTTP code when it calls `SendBytes()`, no?"
  },
  {
   "t": "2025-03-05T11:30:59Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1977960657,
   "text": "If SendBytes() is called from outside the loop then the lock could be held by something else inside the loop, including another call to SendBytes (ie from EventReadyToSend)"
  },
  {
   "t": "2025-03-05T11:33:30Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1977960657,
   "text": "Oh wait. AssertLockNotHeld only applies to the caller? It doesn't require that the mutex is open on all threads and available to lock?"
  },
  {
   "t": "2025-03-05T12:25:16Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 1977960657,
   "text": "Right, only to the caller (same thread). Locked or not by other threads is irrelevant for `AssertLockNotHeld()`."
  },
  {
   "t": "2025-03-05T20:59:09Z",
   "kind": "comment",
   "who": "theuni",
   "assoc": "MEMBER",
   "text": "Sorry for waiting to late to chime in on this, but I'm afraid I'm a concept NACK. We discussed this at length at CoreDev, and I'll briefly try to sum up my thoughts:\n\n- I believe this is the wrong abstraction level. SockMan is essentially mimicking the behavior of a generic io multiplexing framework (libevent, libuv, etc.), but without the feature-set of those libs.\n- The internals and api are heavily biased on Core's p2p layer, and aren't well-suited for abstracting. In particular, its event-loop model, send/recv model, and thread model are all particular to Core, and far from state-of-the-art anyway.\n- It's not well-suited for the 2 use-cases (that I'm aware of) which use it: an http server and sv2 server. Both would benefit from different from custom socket handling and would actually be _more_ straightforward to implement that way as opposed to building on top of SockMan imo.\n- Both of the implementations mentioned above ended up with very non-optimal performance because of the optimistic send, another implementation detail here. This could of course be fixed or worked around, but is an example of the type of problems I would expect to see.\n\nI've discussed this with both @Sjors and @pinheadmz and have agreed to attempt custom implementations for their use-cases to replace their uses of SockMan. As such, I'd rather not see is abstraction go in.\n\nAgain, apologies for the delay in commenting :("
  },
  {
   "t": "2025-03-06T12:54:00Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "@theuni, thanks for looking into this! I find your feedback constructive.\n\nIf we would have different parts of the code listen for incoming connections, accept them, read and write data to them, then there is bound to be some code duplication in that around the low level sockets handling. `SockMan` aims to handle that code duplication. It is an in-house minimalistic version of a generic IO library, not a feature rich competitor to them. Something like [PCP](https://github.com/bitcoin/bitcoin/pull/30043). Only the minimal stuff that's needed.\n\nYes, it is biased on Core's P2P layer, because it is extracted from it. If it does not cover some of the needs of Sv2 or HTTP, I am open to tweaking it in any way possible. At the end it should be the intersection of what's needed for P2P, Sv2 and HTTP. If there is no such intersection, then `SockMan` should not exist, but I think that there is.\n\n[quoted text omitted]\nAlright, I am open to that. I would be interested to review. If it would be better, then I do not insist that `SockMan` is used in Sv2 or HTTP. My idea is to make the life of Sv2 and HTTP easier. Could overhaul `SockMan` in anyway to do that - its current interface is not set in stone. Maybe also worth considering is to have a common library for Sv2 and HTTP and leave P2P (aka `CConnman`) alone, if Sv2 and HTTP have more in common and P2P is odd (i.e. if the intersection of Sv2 and HTTP is big and intersecting that with P2P is small or null)."
  },
  {
   "t": "2025-03-13T08:39:15Z",
   "kind": "comment",
   "who": "fanquake",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nAs far as I'm aware, nothing SV2 related is going into Core (aside from the mining interface), and we aren't planning on refactoring P2P code so it could be copy-pasted out/reused in some other SV2 utility, so, SV2 shouldn't be a motivating usecase here? It looks like #30694 could also be updated to remove any reference to SV2 as motivation (i.e \"Doing Stratum V2\")."
  },
  {
   "t": "2025-03-13T10:23:43Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "The current approach of having the Template Provider communicate via IPC, as described in #31098, indeed does not require Bitcoin Core to change its net code.\n\nMaking easy to copy-paste code could still be a motivation for the author, but it would not be a good reason to include it.\n\nAdditionally, as @theuni pointed out in https://github.com/bitcoin/bitcoin/pull/30988#issuecomment-2702063374, it may not be the right design / abstraction for a Template Provider either."
  },
  {
   "t": "2025-03-13T19:36:19Z",
   "kind": "comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "text": "I just opened https://github.com/bitcoin/bitcoin/pull/32061 which consumes `Sockman` for HTTP. Thanks to @theuni help at coredev I'm now pretty happy with the performance. It obviously doesn't require a p2p refactor -- so we could just review sockman on its own, or in the context of HTTP"
  },
  {
   "t": "2025-03-20T14:23:22Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "f2f9ff9823fa9ef84ab74af675dad36a390a6b34"
  },
  {
   "t": "2025-03-20T14:23:56Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`741f17e51d...f2f9ff9823`: rebase due to conflicts"
  },
  {
   "t": "2025-04-09T08:12:55Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "7ef9661fb09d2cd9356cca7476256fea2077bec9"
  },
  {
   "t": "2025-04-09T08:13:12Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`f2f9ff9823...7ef9661fb0`: rebase due to conflicts"
  },
  {
   "t": "2025-04-10T12:43:09Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "I assume everybody understands the benefits of modular and reusable code. Reusability is actually a metric for code quality. The fact that `SockMan` can be reused from 3 places means this is on the right track. Even if Sv2 and HTTP are completely dropped, this wouldn't change the reusability of `SockMan`. Current `CConnman` is a mixture of low level socket handling and Bitcoin-P2P specifics. IMO this split makes sense even without Sv2 and HTTP because it makes the code more modular and reusable.\n\nCan the current `SockMan` interface be improved? I am sure it can and I am open to all kinds of suggestions, big or small."
  },
  {
   "t": "2025-05-02T05:52:36Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "503791ed57739a5d76aebbb10a39de15dd61eff6"
  },
  {
   "t": "2025-05-02T05:53:04Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`7ef9661fb0...503791ed57`: rebase due to conflicts"
  },
  {
   "t": "2025-05-21T12:02:33Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "9cf56ad085d88f0d5fa1b8751ac1ed04e751a5fd"
  },
  {
   "t": "2025-05-21T12:02:53Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`503791ed57...9cf56ad085`: rebase due to conflicts"
  },
  {
   "t": "2025-05-21T13:28:40Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "a8fcd8385e1ca382378ef0450c742ec714f67e99"
  },
  {
   "t": "2025-05-21T13:29:01Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`9cf56ad085...a8fcd8385e`: fix CI lint"
  },
  {
   "t": "2025-06-13T12:20:33Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4"
  },
  {
   "t": "2025-06-13T12:21:12Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`a8fcd8385e...6aa43a298d`: rebase and address minor suggestion in comment"
  },
  {
   "t": "2025-06-13T19:42:44Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "~Interesting I'm getting `use-of-uninitialized-value` from [MSan](https://cirrus-ci.com/task/6108635308228608?logs=ci#L2378) running SockMan in a unit test. I'm not sure how you are avoiding it on your branch! I will add a default value of `false`~\n\nEdit: never mind, the MSan error is unrelated to this `cancel_recv`"
  },
  {
   "t": "2025-06-16T12:11:00Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 2145965057,
   "text": "Resolved?"
  },
  {
   "t": "2025-06-16T12:15:04Z",
   "kind": "review_comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": 2145965057,
   "text": "Yes"
  },
  {
   "t": "2025-06-23T10:26:08Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "I suggest marking this as draft while there is momentum on the \"lite\" version in #32747. Depending on how that goes, this PR could be rebased on it."
  },
  {
   "t": "2025-06-25T14:43:49Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "Marked as draft as [suggested](https://github.com/bitcoin/bitcoin/pull/30988#issuecomment-2995857066). Will focus on https://github.com/bitcoin/bitcoin/pull/32747, thanks!"
  },
  {
   "t": "2025-08-07T15:39:53Z",
   "kind": "comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "text": "@tdb3 @hodlinator @Sjors I'm hoping to turn your concept ACKs into reviews on the stripped-down version of this PR: https://github.com/bitcoin/bitcoin/pull/32747"
  },
  {
   "t": "2025-08-22T11:39:46Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "@pinheadmz it's on my review list, will do soon(tm)"
  },
  {
   "t": "2025-10-06T11:20:08Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "8725d10df5050f9ac2a7cbedf2502716786fca84"
  },
  {
   "t": "2025-10-06T11:20:25Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`6aa43a298d...8725d10df5`: rebase due to conflicts"
  },
  {
   "t": "2025-10-06T17:39:35Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "dd32dfaaf326f9450a8dbc677c9b05588b00b39d"
  },
  {
   "t": "2025-10-06T17:39:59Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`8725d10df5...dd32dfaaf3`: rebase due to conflicts and fix connman fuzz test."
  },
  {
   "t": "2025-10-07T08:24:58Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "69c015b258c3bbfb44af478d7a5a05ad0b0156b8"
  },
  {
   "t": "2025-10-07T08:25:25Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`dd32dfaaf3...69c015b258`: fix windows compilation"
  },
  {
   "t": "2025-11-04T02:15:40Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK 69c015b258c3bbfb44af478d7a5a05ad0b0156b8 and I've started reviewing this. Overall I think this is a nice change that simplifies net.cpp by removing low level socket calls and details of establishing connections from implementation of the transport protocol. It also seems like it should make net.cpp code more portable if for example we wanted it to use libuv, or a future c++ networking library, or experiment with sandboxing, mesh networking, etc. The [sockman API](https://github.com/vasild/bitcoin/blob/69c015b258c3bbfb44af478d7a5a05ad0b0156b8/src/common/sockman.h) also seems well organized and documented.\n\nI think I agree with @theuni's comment in https://github.com/bitcoin/bitcoin/pull/30988#issuecomment-2702063374 that the http server and sv2 server probably would not benefit very much from using this, but that seems like more of a concern for #32061 than this PR.\n\nI would be interested to know more about how the HTTP and sv2 implementations \"ended up with very non-optimal performance because of the optimistic send\" though because that would seem like something that would be good to correct or at least document in the sockman class even if it will not be used for these purposes."
  },
  {
   "t": "2025-11-04T14:09:14Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db"
  },
  {
   "t": "2025-11-04T14:09:30Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`69c015b258...6091e94750`: rebase due to conflicts"
  },
  {
   "t": "2025-11-04T17:10:21Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "There are a lot of code moves in this PR. I put the following in my `~/.gitconfig`:\n\n```gitconfig\n[diff]\n        colorMoved = dimmed-zebra\n        colorMovedWS = allow-indentation-change\n```\nit makes reviewing such mechanical moves easier."
  },
  {
   "t": "2025-11-17T14:25:35Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "In commit \"net: separate the listening socket from the permissions\" (1b2a87b3700eb114b8849e05bc488394fa8651bc)\n\nIt would seem less fragile to just replace the `m_permissions` field with a more generic field like `std::any m_metadata` or `std::any m_user_data` instead of requiring the application to maintain a separate unordered map and needing to deal with conditions when the map and vector could get out of sync (https://github.com/bitcoin/bitcoin/pull/30988#discussion_r1946267787, https://github.com/bitcoin/bitcoin/pull/30988#discussion_r1916382790)."
  },
  {
   "t": "2025-11-17T15:16:13Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "In commit \"style: modernize the style of SockMan::BindListenPort()\" (6ba0aab8658a7bd553a96e441aa1d5e016e086c3)\n\nCould be good to note in commit message what behaviors this is changing, so it clear they are intentional.  After this commit:\n\n- err_msg / strError output is no longer set if there are minor errors like failing to set SO_REUSEADDR\n- \"Bound to [address]\" log message is dropped and moved to caller\n- Other log messages are changed slightly"
  },
  {
   "t": "2025-11-17T15:32:17Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6aa43a298d10d1de77f34c392b86302522e50bc4",
   "in_reply_to": 1916431512,
   "text": "In commit \"net: split CConnman::AcceptConnection() off CConnman\" (3e881168755392d78d2f9817fe643aadba39aa56)\n\nre: https://github.com/bitcoin/bitcoin/pull/30988#discussion_r1932221561\n\n[quoted text omitted]\nIMO this information would be useful to mention in the commit message"
  },
  {
   "t": "2025-11-17T16:58:58Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "In commit \"net: move CConnman-specific parts away from ThreadI2PAcceptIncoming()\" (07d731b25219cd27a9428d443902f450acc9423c)\n\nNames and comments seem confusing and maybe inaccurate:\n\n- The name STOP_LISTENING implies I2P was previously listening but now stopped, but the comment implies it might have never been listening.\n- The name STOP_LISTENING seems like it could happen for reasons other than failing (like shutting down), so it's inconsistent with the comment which only mentions failing.\n- Comments mention STOP could happen after START, but not that START could happen after STOP, even though the code seems to allow that.\n- Comment implies these notifications are only sent on status changes, but code looks like these will be sent repeatedly even if nothing changes.\n\nWould suggest rewriting and renaming to be clearer. Maybe:\n\n```c++\n// Indicates whether the I2P node is currently listening for new connections.\n// A status update is sent on each accept attempt; it may be identical to the\n// previous update, and the state may temporarily switch between listening and\n// not listening.\nenum class I2PStatus { LISTENING, NOT_LISTENING };\n```"
  },
  {
   "t": "2025-11-17T17:46:37Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "In commit \"net: move I2P-accept-incoming code from CConnman to SockMan\" (3e91c8b502d43d656d9f3207cb550a0c03d1429e)\n\ns/Singal/Signal"
  },
  {
   "t": "2025-11-17T18:28:06Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.cpp",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "In commit \"net: index nodes in CConnman by id\" (63751341d1ba7ba144e221bb8f25fec6293b08b3)\n\nNot important but seems like the `ForNode` and `DisconnectNode` methods changed here could be simpler if they used the `GetNodeById` helper method introduced in the next commit (3bb0f2ddfb88e6b8d1aa5cff77416630ad11a381). Might be good to backport it."
  },
  {
   "t": "2025-11-17T18:58:12Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "path": "src/common/sockman.h",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "in_reply_to": null,
   "text": "In commit \"net: move sockets from CNode to SockMan\" (a59b12aba139aa6003e65ef5842f537a99ae4340)\n\nThis commit is larger than the other commits. It would be nice if there's way to break it up since a few things are moving at the same time."
  },
  {
   "t": "2025-11-17T19:40:52Z",
   "kind": "review",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "6091e947504a13df9ee3310e0fce4585c79d19db",
   "text": "Code review ACK 6091e947504a13df9ee3310e0fce4585c79d19db\n\nI'm about halfway through reviewing this, and so far it seems like has a lot of nice code improvements in addition to conceptual benefits mentioned earlier https://github.com/bitcoin/bitcoin/pull/30988#issuecomment-3483436641.\n\nOne thing I don't like about the API however, is the use of integer connection ids and introduction of hash maps to to tie connection ids, sockets, and application data together:\n\n- `CConnman::m_listen_permissions` - mapping listening addresses to permissions\n- `CConnman::m_nodes` - mapping connection ids to CNodes\n- `IOReadiness::ids_per_sock` - mapping `Sock` pointers to connection ids\n- `SockMan::m_connected``` - mapping connection ids to `Sock` pointers\n\nI think it would be better to:\n\n- Keep the `ListeningSock` struct instead of getting rid of it, and give it a `std::any` member so application data can be associated with it. The data could be passed to `BindAndStartListening` and `EventNewConnectionAccepted`.\n\n- Rename `struct ConnectionSockets` to `class Connection` and add a `std::any` member to it that can hold the `CNode` reference. Delete the `SockMan::Id` type, and use `Connection` references everywhere ids are currently used.\n\nI think this would provide a safer API and simplify code, allowing all the unordered maps to be dropped. Not very important though, since this PR is mostly moving code, and API improvements could be implemented in followups.\n\nI plan to continue reviewing the PR."
  },
  {
   "t": "2025-11-18T18:40:53Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "text": "re: https://github.com/bitcoin/bitcoin/pull/30988#issuecomment-3483436641\n\n[quoted text omitted]\nAfter looking at #32061 more closely, I think I understand @theuni's concern about how the current SockMan interface does not work well for applications like the HTTP server.\n\nAs I understand it, the problem in #32061 is that when an RPC method finishes and a reply is ready, the RPC thread has no clean way to wake up the SockMan thread and tell it there is data ready to send on this connection. The only thing `HTTPRequest::WriteReply` can do is flip the `m_send_ready` flag returned by `SockMan::ShouldTryToSend` from false to true, and then wait for the SockMan loop to wake up on its own (up to a 50ms poll timeout) and notice the change.\n\nThe latest version of #32061 works around this partially by using the \"optimistic send\" pattern: `HTTPRequest::WriteReply` tries to write data directly to the socket itself if no other write is pending. This makes the code more complicated, and it also doesn\u2019t fully solve the underlying problem: if that optimistic write only sends the first chunk of the response, the socket can still sit idle until the next periodic wakeup even though more data is already available in user space.\n\nIMO the SockMan interface could be improved to avoid this pitfall. One way would be to rename `SockMan::SendBytes` to something like `SockMan::TrySendNow`, with documentation that it is only meant to be called from inside the `EventReadyToSend` callback (i.e. when we already know the socket is writable), and maybe in rare cases as an optimization to avoid a thread hop.\n\nAlternately, if we go in the direction suggested in https://github.com/bitcoin/bitcoin/pull/30988#pullrequestreview-3472979975 and callbacks start passing `Connection` objects instead of integer ids, `TrySendNow` could become a `Connection::SendBytes` method that is only ever called from within those callbacks, reducing the chances of it it being misused from other threads.\n\n#32061 probably shouldn\u2019t need to care about any of this if it is rewritten to not use SockMan, but if we did want to generalize SockMan, we could think about adding a thread-safe `SockMan::Wake` method that interrupts its select/poll wait so the event loop can immediately re-run `ShouldTryToSend` and call back into `EventReadyToSend` as needed. Then applications like `HTTPRequest::WriteReply` wouldn't need to do optimistic sends at all, they could just mark the connection as ready-to-send and call `SockMan::Wake()`."
  },
  {
   "t": "2026-03-05T10:23:44Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "IMO this is relevant. I agree with all of suggestions from @ryanofsky and I started implementing those. I am somewhere midway, but keep getting distracted by other things."
  },
  {
   "t": "2026-05-05T14:32:14Z",
   "kind": "comment",
   "who": "rkrux",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK 6091e947504a13df9ee3310e0fce4585c79d19db for the split of CConnman into smaller and modular parts that should be reused by other components such as libevent-less RPC/HTTP server."
  }
 ],
 "labels_log": [
  {
   "t": "2024-09-27T09:09:45Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-09-27T16:23:34Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-09-29T01:02:15Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-09-29T11:27:54Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-10-07T09:27:40Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-10-28T17:52:52Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-11-14T17:33:38Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-11-14T18:59:43Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-12-06T13:05:12Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-12-19T06:54:27Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2024-12-27T15:06:51Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-01-13T14:58:27Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-01-14T17:33:05Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-01-14T19:02:37Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-01-29T22:00:56Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-01-30T15:03:32Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-02-05T16:23:35Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-02-13T12:51:58Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-03-20T10:18:07Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-03-20T14:38:45Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-03-24T22:39:16Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-04-09T08:22:26Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-04-30T19:09:49Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-02T06:58:41Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-20T20:57:15Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-21T13:29:16Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-21T14:20:33Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-05-21T18:37:05Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-25T10:31:51Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-10-06T11:22:42Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-10-24T19:11:17Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-11-04T15:21:14Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-05T10:47:28Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2025-06-25T14:42:29Z",
   "kind": "convert_to_draft",
   "who": "vasild"
  }
 ],
 "text_chars": 86687,
 "text_tokens_estimate": 21671,
 "changed_paths": [
  "src/CMakeLists.txt",
  "src/common/sockman.cpp",
  "src/common/sockman.h",
  "src/net.cpp",
  "src/net.h",
  "src/net_processing.cpp",
  "src/node/txreconciliation.cpp",
  "src/rpc/net.cpp",
  "src/test/denialofservice_tests.cpp",
  "src/test/fuzz/connman.cpp",
  "src/test/fuzz/net.cpp",
  "src/test/fuzz/p2p_handshake.cpp",
  "src/test/fuzz/p2p_headers_presync.cpp",
  "src/test/fuzz/process_message.cpp",
  "src/test/fuzz/process_messages.cpp",
  "src/test/fuzz/util/net.h",
  "src/test/net_peer_connection_tests.cpp",
  "src/test/net_tests.cpp",
  "src/test/util/net.h",
  "test/functional/feature_port.py"
 ],
 "files": [
  {
   "path": "src/CMakeLists.txt",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/common/sockman.cpp",
   "add": 532,
   "del": 0
  },
  {
   "path": "src/common/sockman.h",
   "add": 467,
   "del": 0
  },
  {
   "path": "src/net.cpp",
   "add": 299,
   "del": 552
  },
  {
   "path": "src/net.h",
   "add": 66,
   "del": 134
  },
  {
   "path": "src/net_processing.cpp",
   "add": 9,
   "del": 4
  },
  {
   "path": "src/node/txreconciliation.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/rpc/net.cpp",
   "add": 4,
   "del": 0
  },
  {
   "path": "src/test/denialofservice_tests.cpp",
   "add": 0,
   "del": 6
  },
  {
   "path": "src/test/fuzz/connman.cpp",
   "add": 17,
   "del": 11
  },
  {
   "path": "src/test/fuzz/net.cpp",
   "add": 0,
   "del": 3
  },
  {
   "path": "src/test/fuzz/p2p_handshake.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/test/fuzz/p2p_headers_presync.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/test/fuzz/process_message.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/test/fuzz/process_messages.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/test/fuzz/util/net.h",
   "add": 0,
   "del": 3
  },
  {
   "path": "src/test/net_peer_connection_tests.cpp",
   "add": 5,
   "del": 6
  },
  {
   "path": "src/test/net_tests.cpp",
   "add": 0,
   "del": 9
  },
  {
   "path": "src/test/util/net.h",
   "add": 19,
   "del": 12
  },
  {
   "path": "test/functional/feature_port.py",
   "add": 5,
   "del": 5
  }
 ],
 "test_lines": 109,
 "git": {
  "head": "6091e947504a13df9ee3310e0fce4585c79d19db",
  "head_matches_backup": true,
  "base": "75baff98fcf987735437196a4db1919e390c4bd2",
  "commits": [
   {
    "sha": "1b2a87b370",
    "subject": "net: separate the listening socket from the permissions",
    "files": 2,
    "add": 19,
    "del": 11
   },
   {
    "sha": "24625de406",
    "subject": "net: drop CConnman::ListenSocket",
    "files": 2,
    "add": 15,
    "del": 20
   },
   {
    "sha": "0a22cda725",
    "subject": "net: split CConnman::BindListenPort() off CConnman",
    "files": 5,
    "add": 133,
    "del": 80
   },
   {
    "sha": "6ba0aab865",
    "subject": "style: modernize the style of SockMan::BindListenPort()",
    "files": 4,
    "add": 57,
    "del": 45
   },
   {
    "sha": "3e88116875",
    "subject": "net: split CConnman::AcceptConnection() off CConnman",
    "files": 6,
    "add": 47,
    "del": 39
   },
   {
    "sha": "95265620ce",
    "subject": "style: modernize the style of SockMan::AcceptConnection()",
    "files": 1,
    "add": 11,
    "del": 7
   },
   {
    "sha": "44d4b620f4",
    "subject": "net: move the generation of ids for new nodes from CConnman to SockMan",
    "files": 5,
    "add": 27,
    "del": 12
   },
   {
    "sha": "07d731b252",
    "subject": "net: move CConnman-specific parts away from ThreadI2PAcceptIncoming()",
    "files": 4,
    "add": 62,
    "del": 9
   },
   {
    "sha": "3e91c8b502",
    "subject": "net: move I2P-accept-incoming code from CConnman to SockMan",
    "files": 7,
    "add": 166,
    "del": 87
   },
   {
    "sha": "63751341d1",
    "subject": "net: index nodes in CConnman by id",
    "files": 7,
    "add": 119,
    "del": 98
   },
   {
    "sha": "3bb0f2ddfb",
    "subject": "net: isolate P2P specifics from GenerateWaitSockets()",
    "files": 4,
    "add": 75,
    "del": 14
   },
   {
    "sha": "7765f94b16",
    "subject": "net: isolate P2P specifics from SocketHandlerConnected() and ThreadSocketHandler()",
    "files": 4,
    "add": 54,
    "del": 4
   },
   {
    "sha": "820c157ebb",
    "subject": "net: isolate all remaining P2P specifics from SocketHandlerConnected()",
    "files": 3,
    "add": 141,
    "del": 40
   },
   {
    "sha": "b1fc18e505",
    "subject": "net: split CConnman::ConnectNode()",
    "files": 5,
    "add": 185,
    "del": 104
   },
   {
    "sha": "44f4fed343",
    "subject": "net: tweak EventNewConnectionAccepted()",
    "files": 6,
    "add": 53,
    "del": 28
   },
   {
    "sha": "a59b12aba1",
    "subject": "net: move sockets from CNode to SockMan",
    "files": 15,
    "add": 480,
    "del": 367
   },
   {
    "sha": "6091e94750",
    "subject": "net: move-only: improve encapsulation of SockMan",
    "files": 2,
    "add": 92,
    "del": 92
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "c4327d9e3824f1b0",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}