{
 "number": 34271,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/34271",
 "title": "net_processing: make m_tx_for_private_broadcast optional",
 "author": "vasild",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-01-13T11:32:27Z",
 "updated_at": "2026-08-03T14:24:27Z",
 "age_days": 247,
 "draft": true,
 "labels": [
  "Needs rebase",
  "Private Broadcast"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
 "head_ref": "optional_m_tx_for_private_broadcast",
 "head_repo": "vasild/bitcoin",
 "head_history": [
  {
   "t": "2026-01-13T15:51:08Z",
   "sha": "c218ad88764601814204d65ce21c06c851f04014"
  },
  {
   "t": "2026-02-05T11:55:42Z",
   "sha": "e1600e02a8c46b97efd47e5e6d38cf068483216f"
  },
  {
   "t": "2026-02-25T05:01:21Z",
   "sha": "41f0bc0332ff6942809812bbc76cc5fc38e9389e"
  },
  {
   "t": "2026-05-11T12:45:21Z",
   "sha": "75259b79443cb1c7ff73af505a5f6a00f655e4d8"
  },
  {
   "t": "2026-05-12T12:13:26Z",
   "sha": "87a42882a94f89eb7d418d3f2f3558fb3bc74a17"
  },
  {
   "t": "2026-06-30T14:37:03Z",
   "sha": "14a241f3b3d15ce531b7f384cb63302df5d62e60"
  }
 ],
 "additions": 91,
 "deletions": 40,
 "changed_files": 2,
 "commit_count": 1,
 "size_bucket": "M",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "polespinasa",
      "url": "https://github.com/bitcoin/bitcoin/pull/34271#pullrequestreview-3661806909"
     },
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/34271#pullrequestreview-3662666631"
     }
    ],
    "approach_ack": [
     {
      "login": "l0rinc",
      "url": "https://github.com/bitcoin/bitcoin/pull/34271#pullrequestreview-4299390314"
     }
    ],
    "stale_ack": [
     {
      "login": "optout21",
      "url": "https://github.com/bitcoin/bitcoin/pull/34271#pullrequestreview-4297179554"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35502,
     "title": "refactor: extract per-message helpers from ProcessMessage (move-only)",
     "author": "w0xlt"
    },
    {
     "number": 35406,
     "title": "private broadcast: limit outstanding txs to count of 10,000",
     "author": "instagibbs"
    },
    {
     "number": 35252,
     "title": "net: send decoy transactions via private broadcast",
     "author": "andrewtoth"
    },
    {
     "number": 35016,
     "title": "net: deduplicate private broadcast state and snapshot types",
     "author": "kenji-yamam0to"
    },
    {
     "number": 34628,
     "title": "p2p: Replace per-peer transaction rate-limiting with global rate limits",
     "author": "ajtowns"
    },
    {
     "number": 29700,
     "title": "kernel, refactor: return error status on all fatal errors",
     "author": "ryanofsky"
    }
   ]
  }
 },
 "acks_parsed": {
  "optout21": {
   "kind": "ack",
   "hash": "87a42882a94f89eb7d418d3f2f3558fb3bc74a17",
   "t": "2026-05-15T09:54:51Z",
   "stale": true
  },
  "polespinasa": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-01-14T17:30:15Z",
   "stale": false
  },
  "l0rinc": {
   "kind": "approach_ack",
   "hash": "87a42882a94f89eb7d418d3f2f3558fb3bc74a17",
   "t": "2026-05-15T16:30:08Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 1,
  "concept_ack": 1,
  "approach_ack": 1,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 1,
  "distinct_reviewers": [
   "fanquake",
   "l0rinc",
   "mzumsande",
   "optout21",
   "polespinasa",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": false,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-06-30T14:56:42Z",
  "last_reviewer_activity": "2026-08-03T14:24:21Z",
  "last_reviewer": "fanquake",
  "author_silent_days": 79,
  "waiting_on_author_days": 45,
  "days_since_update": 45
 },
 "refs": {
  "mentioned": [
   29415,
   35252
  ],
  "depends_on": [
   29415
  ],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 29415,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-01-12",
    "title": "Broadcast own transactions only via short-lived Tor or I2P connections"
   },
   {
    "number": 35252,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "net: send decoy transactions via private broadcast"
   }
  ],
  "conflicts": [
   35502,
   35406,
   35252,
   35016,
   34628,
   29700
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/net_processing.cpp"
 ],
 "body": "Make `PeerManagerImpl::m_tx_for_private_broadcast` optional because it is needed only in private broadcast mode (`-privatebroadcast=1`).\n\nA followup to #29415, requested in:\nhttps://github.com/bitcoin/bitcoin/pull/29415#discussion_r2620864832\nhttps://github.com/bitcoin/bitcoin/pull/29415/files#r2620864832\n\n[quoted text omitted]",
 "commits": [
  {
   "sha": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "date": "2026-06-30T13:09:47Z",
   "message": "net_processing: make m_tx_for_private_broadcast optional\n\nMake `PeerManagerImpl::m_tx_for_private_broadcast` optional because it\nis needed only in private broadcast mode (`-privatebroadcast=1`).\n\nRequested in:\nhttps://github.com/bitcoin/bitcoin/pull/29415#discussion_r2620864832\nhttps://github.com/bitcoin/bitcoin/pull/29415/files#r2620864832"
  }
 ],
 "timeline": [
  {
   "t": "2026-01-13T11:40:18Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "340d10d26d0163201045594b30d96edd544a2f93",
   "in_reply_to": null,
   "text": "This change creates a situation that should be impossible: to have a private broadcast connection existent and have the optional `m_tx_for_private_broadcast` empty. Anyway it has to be handled somehow in the code and I think that maybe an `assert()` or `Assume()` is too strong? So here it would behave as if it is not a private broadcast connection. Maybe this is not the correct behavior?\n\nI am `~0` on making `m_tx_for_private_broadcast` optional. In other words, it has some pros and some cons, I am fine either way."
  },
  {
   "t": "2026-01-13T13:14:24Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "340d10d26d0163201045594b30d96edd544a2f93",
   "in_reply_to": 2686032657,
   "text": "That can happen if `InitiateTxBroadcastPrivate()` was not called before. That could happen even without this change, and the behavior is very similar: `m_tx_for_private_broadcast` was initialized with an 'empty' `PrivateBroadcast`, so the `PickTxForSend()`, `GetStale()`, etc. methods would return nothing. However, the difference is that in that case `LogDebug`'s are emitted. An `assert` might be too strong, as there is no guarantee that `InitiateTxBroadcastPrivate()` is called upfront, but the `LogDebug` messages should be emitted the same way if `m_tx_for_private_broadcast` none, or it is set but does not contain the expected transaction.\nAnother way would be to lazily init `m_tx_for_private_broadcast` before every access, but that may be an overkill."
  },
  {
   "t": "2026-01-13T13:23:19Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "340d10d26d0163201045594b30d96edd544a2f93",
   "in_reply_to": null,
   "text": "How about this approach to have the same `LogDebug` for the case when `m_tx_for_private_broadcast` is unset and there is not TX?\n\n```\n        if (pfrom.IsPrivateBroadcastConn()) {\n            const auto pushed_tx_opt{!m_tx_for_private_broadcast.has_value() ? std::nullopt : m_tx_for_private_broadcast->GetTxForNode(pfrom.GetId())};\n            if (!pushed_tx_opt) {\n                LogInfo( ...\n                ...\n            }\n            ...\n        }\n```"
  },
  {
   "t": "2026-01-13T13:30:56Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "ConceptACK\nReview code; verified that all occurence of `m_tx_for_private_broadcast` is touched, verified building and unit tests locally; left some comments."
  },
  {
   "t": "2026-01-13T13:35:15Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "340d10d26d0163201045594b30d96edd544a2f93",
   "in_reply_to": 2686032657,
   "text": "[quoted text omitted]\n\nOk, but how could `pfrom.IsPrivateBroadcastConn()` be `true` if `InitiateTxBroadcastPrivate()` was not called before?"
  },
  {
   "t": "2026-01-13T14:03:42Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "340d10d26d0163201045594b30d96edd544a2f93",
   "in_reply_to": 2686032657,
   "text": "I can't answer that, maybe it can't, but there is no straightforward guarantee in the code; one is a check in `CNode`, one is a call in `PeerManagerImpl`. Regardless whether it is possible (now) or not, I'm saying that the behavior regarding logging should be the same after this change than before (even if it is there is no way to get that combination today, there may be some (incorrect) change in the future, so keeping the log is a good idea)."
  },
  {
   "t": "2026-01-13T15:51:08Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "c218ad88764601814204d65ce21c06c851f04014"
  },
  {
   "t": "2026-01-13T15:53:36Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`340d10d26d0163201045594b30d96edd544a2f93...c218ad88764601814204d65ce21c06c851f04014`: disconnect the peer if private broadcast storage is not initialized and log those events that cannot happen with the current code and must not happen in the future either."
  },
  {
   "t": "2026-01-13T15:55:18Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "340d10d26d0163201045594b30d96edd544a2f93",
   "in_reply_to": 2686418311,
   "text": "Added some logs and disconnect the peer that has `IsPrivateBroadcastConn()` `true`."
  },
  {
   "t": "2026-01-13T15:56:10Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "340d10d26d0163201045594b30d96edd544a2f93",
   "in_reply_to": 2686032657,
   "text": "Yeah, I changed it to disconnect the peer if that ever happens in the future."
  },
  {
   "t": "2026-01-14T17:07:37Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": null,
   "text": "is this check here necessary? `ReattemptPrivateBroadcast` is only called in `StartScheduledTasks` where it is only called if private broadcast option is enabled.\n\nI mean if I understood correctly we should never get into this function if private broadcast mode is not set."
  },
  {
   "t": "2026-01-14T17:13:48Z",
   "kind": "review_comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": null,
   "text": "Is there a case where we can have `node.IsPrivateBroadcastConn() = true` and `m_tx_for_private_broadcast.has_value() = false`? If that's the case maybe we want to close that connection as you did in `PushPrivateBroadcastTx` or `ProcessMessage`? (https://github.com/bitcoin/bitcoin/commit/c218ad88764601814204d65ce21c06c851f04014#diff-6875de769e90cec84d2e8a9c1b962cdbcda44d870d42e4215827e599e11e90e3R3542)\n\nEdit: I see that this was already discussed here https://github.com/bitcoin/bitcoin/pull/34271#discussion_r2686032657 maybe want to do the same?\n(I'm not really familiar with the net processing code maybe I am missing something :) )"
  },
  {
   "t": "2026-01-14T17:30:15Z",
   "kind": "review",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "c218ad88764601814204d65ce21c06c851f04014",
   "text": "Concept ACK\n\nI don't understand why we check if `m_tx_for_private_broadcast` has value inside functions that imply that we are in private broadcast mode."
  },
  {
   "t": "2026-01-14T20:35:42Z",
   "kind": "review",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "c218ad88764601814204d65ce21c06c851f04014",
   "text": "Appraoch ACK"
  },
  {
   "t": "2026-01-15T08:16:40Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": 2691309871,
   "text": "Same answer to the question in the main-thread: https://github.com/bitcoin/bitcoin/pull/34271#pullrequestreview-3661806909 and the question from https://github.com/bitcoin/bitcoin/pull/34271#discussion_r2686032657\n\nIf we don't check whether the optional has value and try to access it and it does not have a value then the program will crash (it is undefined behavior). While this _shouldn't_ be possible in the current code nothing guarantees that future changes will keep it that way. It is not immediately obvious by reading the surrounding code. So, to be more robust, it is better to have some handling of it.\n\nThis is the downside of this change any why I am `~0` about it - it adds visual clutter and unreachable code."
  },
  {
   "t": "2026-01-15T08:30:01Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": 2691329161,
   "text": "[quoted text omitted]\n\nCurrently \"no\", but it is not immediately obvious and not guaranteed to remain like this after future changes.\n\nThis code here is during the connection tear down. It is being disconnected anyway already."
  },
  {
   "t": "2026-02-05T11:55:42Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "e1600e02a8c46b97efd47e5e6d38cf068483216f"
  },
  {
   "t": "2026-02-05T11:55:57Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`c218ad88764601814204d65ce21c06c851f04014...e1600e02a8c46b97efd47e5e6d38cf068483216f`: rebase due to conflicts"
  },
  {
   "t": "2026-02-25T05:01:21Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "41f0bc0332ff6942809812bbc76cc5fc38e9389e"
  },
  {
   "t": "2026-02-25T05:01:46Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`e1600e02a8c46b97efd47e5e6d38cf068483216f...41f0bc0332ff6942809812bbc76cc5fc38e9389e`: rebase due to conflicts"
  },
  {
   "t": "2026-05-11T12:45:21Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "75259b79443cb1c7ff73af505a5f6a00f655e4d8"
  },
  {
   "t": "2026-05-11T12:45:39Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`41f0bc0332ff6942809812bbc76cc5fc38e9389e...75259b79443cb1c7ff73af505a5f6a00f655e4d8`: rebase due to conflicts"
  },
  {
   "t": "2026-05-11T13:10:22Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": null,
   "text": "Can `m_tx_for_private_broadcast` be nullopt here?"
  },
  {
   "t": "2026-05-11T13:10:54Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": null,
   "text": "Can `m_tx_for_private_broadcast` be nullopt here?"
  },
  {
   "t": "2026-05-11T13:12:21Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "75259b79443cb1c7ff73af505a5f6a00f655e4d8",
   "text": "Re-reviewing 75259b79443cb1c7ff73af505a5f6a00f655e4d8 ; left two comments, otherwise LGTM"
  },
  {
   "t": "2026-05-12T12:13:26Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "87a42882a94f89eb7d418d3f2f3558fb3bc74a17"
  },
  {
   "t": "2026-05-12T12:14:08Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": 3219137112,
   "text": "Yes, indeed. Fixed."
  },
  {
   "t": "2026-05-12T12:14:20Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": 3219140878,
   "text": "Same as above, fixed. Thanks!"
  },
  {
   "t": "2026-05-12T12:20:46Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`75259b79443cb1c7ff73af505a5f6a00f655e4d8...87a42882a94f89eb7d418d3f2f3558fb3bc74a17`: fix a silent conflict where new methods were added after this PR was created. Added a guard to them as well. This was the reason for the CI failure.\n\nI wonder if this still makes sense, given the [followups](https://github.com/bitcoin/bitcoin/issues/34476) to the [original PR](https://github.com/bitcoin/bitcoin/pull/29415) where this was suggested. @mzumsande?"
  },
  {
   "t": "2026-05-15T09:54:51Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "87a42882a94f89eb7d418d3f2f3558fb3bc74a17",
   "text": "ACK 87a42882a94f89eb7d418d3f2f3558fb3bc74a17\n\nReview code; verified that all occurrences of `m_tx_for_private_broadcast` are touched, verified building and unit tests locally."
  },
  {
   "t": "2026-05-15T15:35:07Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "87a42882a94f89eb7d418d3f2f3558fb3bc74a17",
   "in_reply_to": null,
   "text": "Shouldn't we do this in `PeerManagerImpl::PeerManagerImpl` instead?\nIf so, would it be possible to cover this case with a test? I'm surprised they all passed.\n\nHere we could log instead:\n```patch\ndiff --git a/src/net_processing.cpp b/src/net_processing.cpp\nindex 9d0eb588db..23a840daa2 100644\n--- a/src/net_processing.cpp\n+++ b/src/net_processing.cpp\n@@ -2026,6 +2026,10 @@ PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman,\n       m_warnings{warnings},\n       m_opts{opts}\n {\n+    if (m_opts.private_broadcast) {\n+        m_tx_for_private_broadcast.emplace();\n+    }\n+\n     // While Erlay support is incomplete, it must be enabled explicitly via -txreconciliation.\n     // This argument can go away after Erlay support is complete.\n     if (opts.reconcile_txs) {\n@@ -2276,11 +2280,12 @@ void PeerManagerImpl::InitiateTxBroadcastToAll(const Txid& txid, const Wtxid& wt\n\n void PeerManagerImpl::InitiateTxBroadcastPrivate(const CTransactionRef& tx)\n {\n-    // Lazily initialize `m_tx_for_private_broadcast` the first time it is needed.\n+    const auto txstr{strprintf(\"txid=%s, wtxid=%s\", tx->GetHash().ToString(), tx->GetWitnessHash().ToString())};\n     if (!m_tx_for_private_broadcast.has_value()) {\n-        m_tx_for_private_broadcast.emplace();\n+        LogDebug(BCLog::PRIVBROADCAST, \"Ignoring request to privately broadcast transaction because private broadcast is not enabled: %s\", txstr);\n+        return;\n     }\n-    const auto txstr{strprintf(\"txid=%s, wtxid=%s\", tx->GetHash().ToString(), tx->GetWitnessHash().ToString())};\n+\n     if (m_tx_for_private_broadcast->Add(tx)) {\n         LogDebug(BCLog::PRIVBROADCAST, \"Requesting %d new connections due to %s\", NUM_PRIVATE_BROADCAST_PER_TX, txstr);\n         m_connman.m_private_broadcast.NumToOpenAdd(NUM_PRIVATE_BROADCAST_PER_TX);\n```\n\nwe could likely verify that a peer manager constructed without private broadcast enabled does not create private-broadcast queue state or request private-broadcast connections when `InitiateTxBroadcastPrivate` is called directly, something like:\n```patch\ndiff --git a/src/test/peerman_tests.cpp b/src/test/peerman_tests.cpp\n--- a/src/test/peerman_tests.cpp\t(revision 419182b0c6b622eee9d19dbd3938c396a494a1ae)\n+++ b/src/test/peerman_tests.cpp\t(revision 22b159e9217b2fbe125e8d6a8fc7baac1359dfc6)\n@@ -6,6 +6,7 @@\n #include <node/miner.h>\n #include <net_processing.h>\n #include <pow.h>\n+#include <primitives/transaction.h>\n #include <test/util/setup_common.h>\n #include <validation.h>\n\n@@ -75,4 +76,18 @@\n     BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK | NODE_WITNESS));\n }\n\n+BOOST_AUTO_TEST_CASE(private_broadcast_disabled_ignores_initiate)\n+{\n+    BOOST_REQUIRE_EQUAL(m_node.connman->m_private_broadcast.NumToOpen(), 0U);\n+\n+    const auto peerman{PeerManager::make(*m_node.connman, *m_node.addrman, /*banman=*/nullptr, *m_node.chainman, *m_node.mempool, *m_node.warnings, {.private_broadcast = false})};\n+    peerman->InitiateTxBroadcastPrivate(MakeTransactionRef(CMutableTransaction{}));\n+\n+    BOOST_CHECK(peerman->GetPrivateBroadcastInfo().empty());\n+    BOOST_CHECK_EQUAL(m_node.connman->m_private_broadcast.NumToOpen(), 0U);\n+}\n+\n BOOST_AUTO_TEST_SUITE_END()\n```"
  },
  {
   "t": "2026-05-15T16:14:27Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": null,
   "text": "Since `std::optional` has a dedicated `operator bool()` with the same semantics as `has_value()`, I think `if (m_tx_for_private_broadcast)` is preferable here.\nIt is idiomatic C++20 and avoids adding noise around already defensive private-broadcast guards. I would reserve `.has_value()` for cases where the boolean value is being named or passed onward, not for simple control flow.\n\n```suggestion\n    if (!m_tx_for_private_broadcast) {\n```\n\n(applies to a few more cases here)"
  },
  {
   "t": "2026-05-15T16:18:37Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": null,
   "text": "Other private-broadcast runtime logs are already almost entirely `LogDebug(BCLog::PRIVBROADCAST, ...)`, consider if this really needs to expose to the logs that we are using private broadcast."
  },
  {
   "t": "2026-05-15T16:26:09Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": null,
   "text": "Note that this still reserves storage for a `PrivateBroadcast` inside every `PeerManagerImpl`, even when private broadcast is disabled.\nThis only skips construction and active state - avoiding the object-size footprint too would require indirection, e.g. `std::unique_ptr<PrivateBroadcast>`."
  },
  {
   "t": "2026-05-15T16:30:08Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "state": "CHANGES_REQUESTED",
   "commit": "87a42882a94f89eb7d418d3f2f3558fb3bc74a17",
   "text": "Approach ACK 87a42882a94f89eb7d418d3f2f3558fb3bc74a17\n\nMaking the private-broadcast transaction state optional is a useful cleanup for the default net-processing path, but my main concern is that the optional is currently initialized from `InitiateTxBroadcastPrivate()` instead of from the peer manager\u2019s private-broadcast option. That means a direct call can create private-broadcast state even when the peer manager was constructed with private broadcast disabled. I left a suggested patch and a small unit test for that boundary.\n\nI also left smaller comments around idiomatic optional checks, matching nearby private-broadcast logging style, and clarifying that `std::optional<PrivateBroadcast>` still reserves inline storage even when disengaged.\n\nCould you also summarize the relevant motivation from the linked #29415 discussion in the PR description or commit message? The links are useful, but having the actual rationale here would make this PR easier to review on its own."
  },
  {
   "t": "2026-06-24T13:29:14Z",
   "kind": "review",
   "who": "mzumsande",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "87a42882a94f89eb7d418d3f2f3558fb3bc74a17",
   "text": "[quoted text omitted]\n\nI think it depends on the general direction in which private broadcast should be taken. I always viewed it as an optional, strictly opt-in module that should be separated well. However, suggestions such as #35252 go into a completely different direction. If in the future even nodes that don't opt into private broadcast (`-privatebroadcast=0`) would send private transactions, then this would move private broadcast into core functionality, and making `m_tx_for_private_broadcast` optional wouldn't make sense."
  },
  {
   "t": "2026-06-30T14:37:03Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60"
  },
  {
   "t": "2026-06-30T14:37:36Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`87a42882a94f89eb7d418d3f2f3558fb3bc74a17...14a241f3b3d15ce531b7f384cb63302df5d62e60`: rebase and take some suggestions."
  },
  {
   "t": "2026-06-30T14:49:31Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "87a42882a94f89eb7d418d3f2f3558fb3bc74a17",
   "in_reply_to": 3249280022,
   "text": "Moved the initialization to `PeerManagerImpl::PeerManagerImpl()`. I am fine either way. Now `PeerManagerImpl::InitiateTxBroadcastPrivate()` contains an unreachable branch too. So we have these patterns in a few methods:\n\n```\nPeerManagerImpl::PrivateBroadcastSpecificMethod()\n{\n    if (m_tx_for_private_broadcast is not initialized) {\n        // But it should be if this method is called,\n        // so there is a bug in the caller.\n        // And now what? Assert() or Assume() seems too strong?\n        log a warning and return something\n    }\n...\n```\n\nI did not take the test because that seems like a bug in the caller. Extended the docs of the methods instead to mention that these methods should only be called if private broadcast has been enabled when constructing the peer manager."
  },
  {
   "t": "2026-06-30T14:52:23Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": 3249495961,
   "text": "I prefer the `.has_value()` because it hints the human reader that this is a `std:optional` instead of `bool`, `unique_ptr`, `int` or whatever other type. Similar to why I prefer to use `if (a != 0)` for integers instead of `if (a)`."
  },
  {
   "t": "2026-06-30T14:54:48Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/net_processing.cpp",
   "commit": "14a241f3b3d15ce531b7f384cb63302df5d62e60",
   "in_reply_to": 3249518702,
   "text": "This is a serious enough case to warrant a `LogWarning()`. It shows a that there is a bug in the callers, but somehow I feel that maybe `Assume()` is too strong here. Or maybe not?"
  },
  {
   "t": "2026-06-30T14:56:42Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "@mzumsande, right, I agree. And I do not have an answer."
  },
  {
   "t": "2026-08-03T14:24:21Z",
   "kind": "comment",
   "who": "fanquake",
   "assoc": "MEMBER",
   "text": "Moved to draft for now, given it needs rebase, and there needs to be some decisions made on general private-broadcast direction."
  }
 ],
 "labels_log": [
  {
   "t": "2026-01-27T13:29:20Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-05T11:58:42Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-19T23:08:39Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-25T06:56:44Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-11T13:47:25Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-21T13:55:22Z",
   "action": "labeled",
   "label": "Private Broadcast",
   "who": "fanquake"
  },
  {
   "t": "2026-05-11T13:18:23Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-11T13:45:24Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-12T13:12:55Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-07T15:00:23Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-08-03T14:24:12Z",
   "kind": "convert_to_draft",
   "who": "fanquake"
  }
 ],
 "text_chars": 14093,
 "text_tokens_estimate": 3523,
 "changed_paths": [],
 "files": [],
 "test_lines": null,
 "git": null,
 "input_hash": "1a9e30e3b5196074",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}