{
 "number": 34824,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/34824",
 "title": "net: encapsulate TxRelay state and replace recursive mutexes",
 "author": "w0xlt",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-03-14T08:07:38Z",
 "updated_at": "2026-09-17T04:24:40Z",
 "age_days": 187,
 "draft": false,
 "labels": [
  "P2P"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "4ac05f01c11e6e27a725249054f7c54e756874e5",
 "head_ref": "refactor/replace-txrelay-recursive-mutexes",
 "head_repo": "w0xlt/bitcoin",
 "head_history": [
  {
   "t": "2026-03-15T18:51:59Z",
   "sha": "8ed9eb0b2401c0ef77bd93b7ab30b13916067f71"
  },
  {
   "t": "2026-03-24T20:58:55Z",
   "sha": "475366b55a9d8010ab81e82d7c5f4d0e95803bbf"
  },
  {
   "t": "2026-05-17T07:33:00Z",
   "sha": "67527c70975e1c3bb4918ba0ebeb4313d8c741be"
  },
  {
   "t": "2026-06-13T07:55:33Z",
   "sha": "dab04e0c393cfc6ce83f0be764ac63e90cd0098b"
  },
  {
   "t": "2026-06-16T18:45:59Z",
   "sha": "73d972ba77de69be564601f9118e3d9a49f5fe1a"
  },
  {
   "t": "2026-06-17T17:25:10Z",
   "sha": "d88df07a820d01d10c1d74b31e673ff1ec836897"
  },
  {
   "t": "2026-06-30T23:29:15Z",
   "sha": "5a34f55d2acf80f4396e21c8e7d80258801729b2"
  },
  {
   "t": "2026-07-01T00:52:56Z",
   "sha": "e8dbc6e4d5799639baeaf909c9e2a41406ee4c2d"
  },
  {
   "t": "2026-09-15T20:04:44Z",
   "sha": "4ac05f01c11e6e27a725249054f7c54e756874e5"
  }
 ],
 "additions": 803,
 "deletions": 192,
 "changed_files": 7,
 "commit_count": 6,
 "size_bucket": "L",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "hebasto",
      "url": "https://github.com/bitcoin/bitcoin/pull/34824#pullrequestreview-3955046291"
     },
     {
      "login": "theuni",
      "url": "https://github.com/bitcoin/bitcoin/pull/34824#issuecomment-4086113944"
     }
    ],
    "stale_ack": [
     {
      "login": "sedited",
      "url": "https://github.com/bitcoin/bitcoin/pull/34824#pullrequestreview-4305288104"
     },
     {
      "login": "pablomartin4btc",
      "url": "https://github.com/bitcoin/bitcoin/pull/34824#pullrequestreview-4612903742"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35874,
     "title": "p2p: prevent BIP35 requesters from bypassing inbound tx-relay capacity",
     "author": "l0rinc"
    },
    {
     "number": 35511,
     "title": "RFC: consensus: Make `CAmount` a class",
     "author": "hodlinator"
    }
   ]
  }
 },
 "acks_parsed": {
  "sedited": {
   "kind": "ack",
   "hash": "67527c70975e1c3bb4918ba0ebeb4313d8c741be",
   "t": "2026-05-17T10:28:58Z",
   "stale": true
  },
  "theuni": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-03-18T22:58:01Z",
   "stale": false
  },
  "pablomartin4btc": {
   "kind": "ack",
   "hash": "e8dbc6e4d5799639baeaf909c9e2a41406ee4c2d",
   "t": "2026-07-01T21:12:54Z",
   "stale": true
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 2,
  "concept_ack": 1,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 2,
  "changes_requested": 0,
  "distinct_reviewers": [
   "fanquake",
   "hebasto",
   "maflcko",
   "pablomartin4btc",
   "sedited",
   "theuni"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-15T21:02:54Z",
  "last_reviewer_activity": "2026-07-01T21:12:54Z",
  "last_reviewer": "pablomartin4btc",
  "author_silent_days": 1,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   19303
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 19303,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Replace all of the RecursiveMutex instances with the Mutex ones"
   }
  ],
  "conflicts": [
   35874,
   35511
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/net_processing.cpp",
  "src/node/txrelay.h"
 ],
 "body": "Part of #19303.\n\nThis PR refactors per-peer transaction relay state so `TxRelay` owns its guarded data and lock boundaries before replacing its `RecursiveMutex` members with plain `Mutex`.\n\nBefore this change, `Peer::TxRelay` was mostly a collection of public state inside `net_processing.cpp`.\nCallers directly locked its mutexes, inspected its fields, and mutated its containers.\nThat made the mutex type change hard to review: replacing `RecursiveMutex` with `Mutex` is only clearly safe if outside code cannot accidentally recurse, take locks in the wrong order, or bypass the intended state transitions.\n\nThe main changes are:\n\n- Move `Peer::TxRelay` into `node::TxRelay`.\n- Route bloom-filter and transaction-inventory state changes through named `TxRelay` methods.\n- Remove direct `TxRelay` field and mutex access from `net_processing.cpp`.\n- Snapshot queued transaction inventory into a `TxInventoryBatch` in `SendMessages()`, process it without holding the `TxRelay` mutex, and merge unsent entries back afterward.\n- Hide the batch backing storage behind a small semantic API.\n- Add unit tests and a fuzz target for the extracted `TxRelay` behavior.\n- Replace the two `RecursiveMutex` members with `Mutex` and annotate the inventory-before-bloom lock order.\n\nNo P2P behavior change is intended. The purpose is to move from \"external code locks and mutates TxRelay internals\" to \"TxRelay owns its state transitions and locking contract.\"\n\nThe most important commit to review is the `SendMessages()` batch snapshot refactor, because that is where the lock ownership model changes. The final mutex replacement is intentionally small after the state has been encapsulated.",
 "commits": [
  {
   "sha": "05fdc96a4955a32962672a7e4d51025b52c6a7a5",
   "date": "2026-09-15T18:30:15Z",
   "message": "refactor: extract node TxRelay helpers\n\nMove the per-peer transaction relay state and helper methods out of\nnet_processing.cpp into a new node::TxRelay type.\n\nKeep the existing locking model and behavior intact while giving the\ntransaction relay code a single home. Net processing still owns\nthe relay flow. This commit only moves the state, bloom-filter\nhelpers, and inventory helpers so later commits can tighten the\ninterface without mixing in behavior changes."
  },
  {
   "sha": "40cc570b911e2cbd3451f76dcfbb408b9d45b009",
   "date": "2026-09-15T19:57:19Z",
   "message": "refactor: snapshot TxRelay inventory in SendMessages\n\nMove the remaining SendMessages transaction-inventory access behind\nTxRelay methods.\n\nStartTxInventoryBatch() snapshots queued inventory while holding the\nTxRelay mutex, lets SendMessages drain the batch without holding that\nmutex, and ReturnTxInventory() merges unsent entries back into the live\nqueue.\n\nThis removes the remaining direct TxRelay state access from\nnet_processing.cpp and makes the guarded TxRelay data private without\nchanging intended relay behavior.\n\nDocument the caller serialization required across relay scheduling and\nbatch processing, provided by g_msgproc_mutex in SendMessages()."
  },
  {
   "sha": "1429054b8c02fec580232bff69611a9717d13057",
   "date": "2026-09-15T19:57:19Z",
   "message": "refactor: hide TxRelay inventory batch storage\n\nMake TxInventoryBatch move-only and hide its backing vector from\nnet_processing.cpp.\n\nExpose semantic operations for batch state, queued candidate snapshots,\ndraining, clearing, and erasure. ReturnTxInventory() takes the batch\nobject itself. Candidate snapshots and drains return owned vectors\nwithout exposing references to batch storage."
  },
  {
   "sha": "b88a86f5ab675d61bfc0f12977eb23e2ed7c1389",
   "date": "2026-09-15T19:57:19Z",
   "message": "test: add node TxRelay unit tests\n\nAdd focused unit coverage for node::TxRelay's public behavior:\nbloom-filter lifecycle, inventory gating before the first send schedule,\nknown-inventory deduplication, state accessors, BIP37 merkle block\ncreation, and the snapshot/return inventory-batch API (including\nconservation across repeated drain rounds and under concurrent\nPushInventory()).\n\nThese tests exercise TxRelay entirely through its public interface and\nprovide guardrails for the private-state and locking changes."
  },
  {
   "sha": "5fa20d34d5070df5512d4a43791aa386177af54b",
   "date": "2026-09-15T19:57:19Z",
   "message": "fuzz: add node TxRelay target\n\nAdd a fuzz target that exercises TxRelay's public operations in random\norder: relay flag changes, bloom-filter updates, known transaction\ninserts, send scheduling, inventory queueing, fee-filter state, and the\nsnapshot/return inventory-batch API.\n\nSeed the test random context because CRollingBloomFilter uses global\nrandom state. This keeps the target deterministic for a given fuzz\ninput."
  },
  {
   "sha": "4ac05f01c11e6e27a725249054f7c54e756874e5",
   "date": "2026-09-15T19:57:34Z",
   "message": "refactor: use Mutex in TxRelay\n\nReplace TxRelay's RecursiveMutex members with plain Mutex now that\ncallers no longer take those locks directly.\n\nAnnotate the bloom-filter mutex as acquired after the inventory mutex,\nmatching the only nested locking path in StartTxInventoryBatch().\nRequire callers of inventory-locking methods to hold neither mutex so\nthread-safety analysis also rejects reversed lock order through helper\ncalls, while retaining the checks against recursive locking."
  }
 ],
 "timeline": [
  {
   "t": "2026-03-14T09:09:20Z",
   "kind": "review",
   "who": "sedited",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "ed2a83c9e4770b308d8bef5e46460cf10f08f96a",
   "text": "Concept ACK\n\nI looked this over and also didn't find any cases where locking recursion would be required, but the change seems brittle to me. Not sure if just changing these is the best way to handle this."
  },
  {
   "t": "2026-03-15T18:51:59Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "8ed9eb0b2401c0ef77bd93b7ab30b13916067f71"
  },
  {
   "t": "2026-03-15T19:39:33Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Thanks @sedited  for the feedback.\n\nThe original PR intentionally included only the first commit to avoid non-trivial refactoring and focus on the `RecursiveMutex` conversion. After your concerns that this minimal approach might be brittle, I took a second pass.\n\nA broader refactor seems justified: both `TxRelay` and `net_processing` benefit from clearer separation of concerns and improved modularization. The main changes are:\n\n* `TxRelay` is now a dedicated component in `src/node/txrelay.h`, instead of being embedded in `src/net_processing.cpp`.\n\n* `SendMessages()` and related call sites now use intent-named operations (`ConsumeSendMempool`, `SetNextInvSendTime`, `TxInventoryKnownInsert`, etc.) rather than mutating fields directly, which reduces local complexity.\n\n* Locking contracts are now explicit at API boundaries (`EXCLUSIVE_LOCKS_REQUIRED`, `LOCK_RETURNED`), improving static thread-safety checking and reviewability.\n\n* State transitions/invariants are centralized in `TxRelay` methods (`PushInventory`, `VerifyInventoryPristine`, `ClearTxInventoryToSendIfNoRelayTxs`), reducing the risk of missed field updates.\n\n* Lock-order reasoning is clearer because lock acquisition sites are now structured and annotated rather than scattered raw member-lock uses.\n\n* Future changes to `TxRelay` internals (containers/fields) should require fewer net-processing edits, since call sites depend more on behavior methods than storage details."
  },
  {
   "t": "2026-03-16T16:12:20Z",
   "kind": "review",
   "who": "hebasto",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "8ed9eb0b2401c0ef77bd93b7ab30b13916067f71",
   "text": "[quoted text omitted]\n\nConcept ACK."
  },
  {
   "t": "2026-03-18T22:58:01Z",
   "kind": "comment",
   "who": "theuni",
   "assoc": "MEMBER",
   "text": "Concept ACK.\n\n[quoted text omitted]\nSee also the `ACQUIRED_BEFORE`/`ACQUIRED_AFTER` annotations which may be useful when there's an established ordering that should be followed.\n\n(Note that unless `-Wthread-safety-beta` is used, clang 22 is required to actually see the warnings: https://github.com/llvm/llvm-project/pull/152853)"
  },
  {
   "t": "2026-03-19T06:26:45Z",
   "kind": "comment",
   "who": "fanquake",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\n(also note that we currently use Clang 22.1.2 in the CI)"
  },
  {
   "t": "2026-03-20T12:11:04Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/node/txrelay.h",
   "commit": "8ed9eb0b2401c0ef77bd93b7ab30b13916067f71",
   "in_reply_to": null,
   "text": "Can we make these private now? `m_tx_inventory_mutex` is still annotated outside this class, but I think that annotation can be dropped now?"
  },
  {
   "t": "2026-03-20T12:58:19Z",
   "kind": "review",
   "who": "sedited",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "8ed9eb0b2401c0ef77bd93b7ab30b13916067f71",
   "text": "Approach ACK\n\nI think I prefer this over a bare bones mutex type change. The changes in the first four commits are pretty straight forward and make sense to me. The last commit is a bit more involved, but git color coding the moved bits does help a bit. I'd be curious to see what others think."
  },
  {
   "t": "2026-03-24T20:58:55Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "475366b55a9d8010ab81e82d7c5f4d0e95803bbf"
  },
  {
   "t": "2026-03-24T21:41:56Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/txrelay.h",
   "commit": "8ed9eb0b2401c0ef77bd93b7ab30b13916067f71",
   "in_reply_to": 2965429233,
   "text": "Done. Thanks."
  },
  {
   "t": "2026-03-24T21:42:47Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "`ACQUIRED_BEFORE`/`ACQUIRED_AFTER` annotations added. Tested with clang 18 and `-Wthread-safety-beta`."
  },
  {
   "t": "2026-05-03T12:13:22Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/net_processing.cpp",
   "commit": "7ccd50834a224b91a95cc8751e9f67313ca8bf07",
   "in_reply_to": null,
   "text": "Nit (include order): This should go between txreconciliation and warnings."
  },
  {
   "t": "2026-05-03T12:23:01Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/node/txrelay.h",
   "commit": "67527c70975e1c3bb4918ba0ebeb4313d8c741be",
   "in_reply_to": null,
   "text": "I'm getting a clang tidy warning here (and for the other one):\n```\n/home/user/bitcoin/src/node/txrelay.h:123:38: error: no matching literal operator for call to 'operator\"\"s' with argument of type 'unsigned long long' or 'const char *', and no matching literal operator template [clang-diagnostic-error]\n  123 |         if (m_next_inv_send_time == 0s) return;\n      |                                      ^\n```\nCan you do `using namespace std::chrono_literals;` like we do in random.h?"
  },
  {
   "t": "2026-05-03T12:47:00Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/node/txrelay.h",
   "commit": "4ac05f01c11e6e27a725249054f7c54e756874e5",
   "in_reply_to": null,
   "text": "In commit 475366b55a9d8010ab81e82d7c5f4d0e95803bbf\n\nCan we make all the data fields private then?"
  },
  {
   "t": "2026-05-03T13:26:47Z",
   "kind": "review",
   "who": "sedited",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "475366b55a9d8010ab81e82d7c5f4d0e95803bbf",
   "text": "ACK 475366b55a9d8010ab81e82d7c5f4d0e95803bbf\n\nI'm not sure about the last commit. It is a bit noisy, but I also like the additional assurances and encapsulation."
  },
  {
   "t": "2026-05-17T07:33:00Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "67527c70975e1c3bb4918ba0ebeb4313d8c741be"
  },
  {
   "t": "2026-05-17T10:28:58Z",
   "kind": "review",
   "who": "sedited",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "67527c70975e1c3bb4918ba0ebeb4313d8c741be",
   "text": "re-ACK 67527c70975e1c3bb4918ba0ebeb4313d8c741be"
  },
  {
   "t": "2026-05-17T19:01:38Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "7ccd50834a224b91a95cc8751e9f67313ca8bf07",
   "in_reply_to": 3178091725,
   "text": "Done. Thanks."
  },
  {
   "t": "2026-05-17T19:01:43Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/txrelay.h",
   "commit": "67527c70975e1c3bb4918ba0ebeb4313d8c741be",
   "in_reply_to": 3178102815,
   "text": "Done. Thanks."
  },
  {
   "t": "2026-05-17T19:01:46Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/txrelay.h",
   "commit": "4ac05f01c11e6e27a725249054f7c54e756874e5",
   "in_reply_to": 3178130417,
   "text": "Done. Thanks."
  },
  {
   "t": "2026-06-07T17:47:19Z",
   "kind": "review",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "67527c70975e1c3bb4918ba0ebeb4313d8c741be",
   "text": "Concept ACK. The first commits look straightforward (as [said earlier](https://github.com/bitcoin/bitcoin/pull/34824#pullrequestreview-3981158291)), and commit 5 seems to preserve the existing lock order while moving the remaining `TxRelay` state access in `SendMessages()` behind annotated helpers.\n\nNon-blocking/ follow-up idea: after this PR, it may be worth considering a small refactor of the tx-relay section of `SendMessages()`. The code could maybe be split into a few conceptual helpers for periodic relay scheduling, BIP35 mempool responses, and regular tx-inventory relay selection. I would not suggest perhaps one helper per comment/ filter condition, but grouping those areas into conceptual helpers could make SendMessages() cleaner and easier to follow, while keeping the change behaviour-preserving."
  },
  {
   "t": "2026-06-12T05:17:38Z",
   "kind": "review",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "67527c70975e1c3bb4918ba0ebeb4313d8c741be",
   "text": "ACK https://github.com/bitcoin/bitcoin/commit/67527c70975e1c3bb4918ba0ebeb4313d8c741be.\n\nChecked both `m_bloom_filter_mutex` and `m_tx_inventory_mutex` in `Peer::TxRelay` aren't acquired recursively, makes sense to move them into non-recursive mutexes (the 18 lock sites mentioned in the description are enumerated in the first commit body for reference).\n\nThe PR introduces `ACQUIRED_BEFORE(m_tx_inventory_mutex)` on `m_bloom_filter_mutex` (and `ACQUIRED_AFTER` on the other side) - as [suggested](https://github.com/bitcoin/bitcoin/pull/34824#issuecomment-4086113944). This establishes a lock ordering that didn't exist before, that ordering annotation becomes load-bearing for deadlock prevention, not just documentation.\n\nAlso, reviewed the replacement of direct member access to `TxRelay` internals with accessor methods. Members are now private \u2014 the only access path is through accessors that either self-lock (negative capability !mutex) or require the caller to hold the lock, with Clang TSA enforcing both at compile time."
  },
  {
   "t": "2026-06-12T11:19:06Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/net_processing.cpp",
   "commit": "dab04e0c393cfc6ce83f0be764ac63e90cd0098b",
   "in_reply_to": null,
   "text": "05b3bbcb0e2b7d66a8107c26b837cb68f4eb5dc0: idk, what is the point of this pull request?\n\nJust changing a RecursiveMutex to a Mutex without proper TSA doesn't make sense, imo. This just risks unsafe code down the line in the future.\n\nThis pull is now moving locking to happen in different files/classes, and still exposes the mutex field globally, just like before.\n\nI don't see how this makes anything safer/easier.\n\nEither, this should be done properly or not at all. I know this isn't easy, which is probably why it hasn't been done for such a long time, but that shouldn't be an excuse to do it improperly."
  },
  {
   "t": "2026-06-12T11:20:18Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/node/txrelay.h",
   "commit": "05b3bbcb0e2b7d66a8107c26b837cb68f4eb5dc0",
   "in_reply_to": null,
   "text": "05b3bbcb0e2b7d66a8107c26b837cb68f4eb5dc0: Is this pattern even safe?\n\nDoes clang TSA detect if callable were to lock the mutex and thus lead to UB?"
  },
  {
   "t": "2026-06-12T11:21:35Z",
   "kind": "review",
   "who": "maflcko",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "67527c70975e1c3bb4918ba0ebeb4313d8c741be",
   "text": "https://github.com/bitcoin/bitcoin/issues/19303#issuecomment-646956780"
  },
  {
   "t": "2026-06-13T07:55:33Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "dab04e0c393cfc6ce83f0be764ac63e90cd0098b"
  },
  {
   "t": "2026-06-13T08:56:30Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "dab04e0c393cfc6ce83f0be764ac63e90cd0098b",
   "in_reply_to": 3402931276,
   "text": "Updated. Direct `TxRelay` data access is gone and state changes now go through TSA-annotated helpers. The remaining mutex accessors are kept for the existing `SendMessages()` batching/lock-order annotations."
  },
  {
   "t": "2026-06-13T08:56:36Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/txrelay.h",
   "commit": "05b3bbcb0e2b7d66a8107c26b837cb68f4eb5dc0",
   "in_reply_to": 3402938119,
   "text": "Done. Removed this callback pattern. The filtered-block path now uses `TxRelay::MakeMerkleBlock()` instead."
  },
  {
   "t": "2026-06-13T08:56:44Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Update after the latest changes:\n\nThe specific callback/TSA concern raised above has been addressed: `WithBloomFilterIfSet()` was removed, and the filtered-block path now uses `TxRelay::MakeMerkleBlock()`, so `CMerkleBlock` construction happens directly under `TxRelay`'s bloom-filter mutex without passing an arbitrary callable while the mutex is held.\n\nThe broader goal here is not just \u201cremove `RecursiveMutex`\u201d for its own sake. The intent is to make the locking logic easier to follow. Compared to the original minimal mutex-type change, the current branch now:\n- moves `TxRelay` into its own component,\n- makes the guarded data private,\n- routes state transitions through named, TSA-annotated methods,\n- documents/enforces the `m_tx_inventory_mutex -> m_bloom_filter_mutex` lock order,\n- adds unit coverage and a fuzz target for the extracted `TxRelay` behavior.\n\nThere is still some intentional mutex exposure through `GetBloomFilterMutex()` / `GetTxInventoryMutex()` because `SendMessages()` currently batches work while holding locks across multiple relay-policy steps.\n\nI think keeping that structure avoids a larger behavioral refactor in this PR, while still making the existing locking more explicit and reviewable.\n\nThis should make future follow-ups easier, since `TxRelay` state transitions are now centralized, annotated, and independently tested."
  },
  {
   "t": "2026-06-14T01:24:51Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Regarding the new `txrelay` fuzz target, it exercises the public `TxRelay` API while tracking the expected relay state and checking `TxRelay` against it after each operation.\n\nIt covers bloom-filter lifecycle, merkle-block creation, known-inventory handling, queued announcement drain/erase paths, BIP35 mempool one-shot handling, fee-filter, and sequence bookkeeping while accounting for rolling-bloom false positives."
  },
  {
   "t": "2026-06-15T07:55:37Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nYeah, and those aren't check with Clang TSA, are they? I am just not convinced that moving to a non-recursive mutex is useful, when it is less safe and requires manual review to avoid UB.\n\nAll of the changes you list have nothing to do with the mutex change. It is possible to do all of the changes independently while keeping the recursive mutex.\n\nSome of the changes are required to make the mutex non-recursive, but I don't think they are sufficient. IIUC making the data and the mutex private (and not exposing references to them) are required changes. However, by exposing references to them in a public getter, those changes are basically undone.\n\nI know this pattern may not have been followed in the past (in https://github.com/bitcoin/bitcoin/issues/19303 related pulls and elsewhere), but I think there is value in first defining a set of possible safe code patterns, and then apply them consistently and consequently. IIUC the pattern is that non-recursive mutexes can be used for fully private fields and that recursive mutexes should be used for globals/public fields? If so, then I think mixing the patterns does not make sense."
  },
  {
   "t": "2026-06-16T18:45:59Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "73d972ba77de69be564601f9118e3d9a49f5fe1a"
  },
  {
   "t": "2026-06-17T17:25:10Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "d88df07a820d01d10c1d74b31e673ff1ec836897"
  },
  {
   "t": "2026-06-17T20:16:16Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Thanks for the feedback. The concern is valid.\n\nThe original goal of this PR was fairly small, just replacing those variables with Mutex, and I was trying to keep the change scoped and avoid a larger `TxRelay` refactor.\n\nTo address the mutex exposure issue, I reworked the branch so that encapsulation comes first, with the mutex replacement as the final step.\n\nThe public mutex accessors have been removed, the guarded state is now private, and `net_processing.cpp` no longer locks or mutates `TxRelay` internals directly.\n\nThe `SendMessages()` path now uses an opaque `TxInventoryBatch` to snapshot and retrieve queued inventory from `TxRelay`.\n\nI updated the PR title and description to better reflect the current scope. The PR has evolved beyond a simple mutex replacement and now introduces a new networking component."
  },
  {
   "t": "2026-06-17T20:16:27Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Unrelated CI error"
  },
  {
   "t": "2026-06-19T10:30:22Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/node/txrelay.h",
   "commit": "05b3bbcb0e2b7d66a8107c26b837cb68f4eb5dc0",
   "in_reply_to": 3402938119,
   "text": "I think the pattern could work, but only if the mutex getter is not defined and only exists to provide compile-time annotations: https://github.com/bitcoin/bitcoin/issues/19303#issuecomment-4750464446"
  },
  {
   "t": "2026-06-20T07:29:39Z",
   "kind": "review_comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/txrelay.h",
   "commit": "05b3bbcb0e2b7d66a8107c26b837cb68f4eb5dc0",
   "in_reply_to": 3402938119,
   "text": "Thanks, that makes sense.\nThe current version no longer has the public mutex getter or the callback-under-lock pattern.\nThe filtered block path now uses `TxRelay::MakeMerkleBlock()`, and the mutexes are not exposed, so I think this concern is now addressed."
  },
  {
   "t": "2026-06-29T16:29:46Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "I think there is a silent merge conflict here."
  },
  {
   "t": "2026-06-30T23:29:15Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "5a34f55d2acf80f4396e21c8e7d80258801729b2"
  },
  {
   "t": "2026-07-01T00:52:56Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "e8dbc6e4d5799639baeaf909c9e2a41406ee4c2d"
  },
  {
   "t": "2026-07-01T07:12:43Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Rebased. Thanks"
  },
  {
   "t": "2026-07-01T21:12:54Z",
   "kind": "review",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "e8dbc6e4d5799639baeaf909c9e2a41406ee4c2d",
   "text": "re-ACK e8dbc6e4d5799639baeaf909c9e2a41406ee4c2d\n\nSince my last ACK: `WithBloomFilterIfSet()` and the public mutex getter were removed - addressing maflcko's concerns that exposing the mutex reference undoes the TSA safety guarantees. State changes now go exclusively through `EXCLUSIVE_LOCKS_REQUIRED(!mutex)` annotated helpers, and `MakeMerkleBlock()` replaces the callable-under-lock pattern."
  },
  {
   "t": "2026-09-15T20:04:44Z",
   "kind": "force_push",
   "who": "w0xlt",
   "commit": "4ac05f01c11e6e27a725249054f7c54e756874e5"
  },
  {
   "t": "2026-09-15T21:02:54Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Rebased."
  }
 ],
 "labels_log": [
  {
   "t": "2026-03-14T08:07:58Z",
   "action": "labeled",
   "label": "P2P",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-16T19:58:19Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-19T10:31:21Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "maflcko"
  },
  {
   "t": "2026-06-19T10:56:38Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-01T01:54:24Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-25T12:19:23Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T20:06:37Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-06-16T18:49:59Z",
   "kind": "renamed",
   "who": "w0xlt",
   "from": "net: refactor: replace Peer::TxRelay RecursiveMutex instances with Mutex",
   "to": "net: encapsulate TxRelay state and replace recursive mutexes"
  }
 ],
 "text_chars": 15963,
 "text_tokens_estimate": 3990,
 "changed_paths": [
  "src/net_processing.cpp",
  "src/net_processing.h",
  "src/node/txrelay.h",
  "src/test/CMakeLists.txt",
  "src/test/fuzz/CMakeLists.txt",
  "src/test/fuzz/txrelay.cpp",
  "src/test/txrelay_tests.cpp"
 ],
 "files": [
  {
   "path": "src/net_processing.cpp",
   "add": 114,
   "del": 190
  },
  {
   "path": "src/net_processing.h",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/node/txrelay.h",
   "add": 280,
   "del": 0
  },
  {
   "path": "src/test/CMakeLists.txt",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/test/fuzz/CMakeLists.txt",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/test/fuzz/txrelay.cpp",
   "add": 95,
   "del": 0
  },
  {
   "path": "src/test/txrelay_tests.cpp",
   "add": 310,
   "del": 0
  }
 ],
 "test_lines": 407,
 "git": {
  "head": "4ac05f01c11e6e27a725249054f7c54e756874e5",
  "head_matches_backup": true,
  "base": "57721f8074c2e8ccfe4c6d10114d4683dca58118",
  "commits": [
   {
    "sha": "05fdc96a49",
    "subject": "refactor: extract node TxRelay helpers",
    "files": 2,
    "add": 189,
    "del": 86
   },
   {
    "sha": "40cc570b91",
    "subject": "refactor: snapshot TxRelay inventory in SendMessages",
    "files": 3,
    "add": 181,
    "del": 107
   },
   {
    "sha": "1429054b8c",
    "subject": "refactor: hide TxRelay inventory batch storage",
    "files": 2,
    "add": 38,
    "del": 13
   },
   {
    "sha": "b88a86f5ab",
    "subject": "test: add node TxRelay unit tests",
    "files": 2,
    "add": 311,
    "del": 0
   },
   {
    "sha": "5fa20d34d5",
    "subject": "fuzz: add node TxRelay target",
    "files": 2,
    "add": 96,
    "del": 0
   },
   {
    "sha": "4ac05f01c1",
    "subject": "refactor: use Mutex in TxRelay",
    "files": 1,
    "add": 15,
    "del": 13
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "8d3bb9b6b63bcede",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}