{
 "number": 36482,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36482",
 "title": "wallet: Only broadcast resubmitted transactions that were added to the mempool",
 "author": "achow101",
 "author_association": "MEMBER",
 "created_at": "2026-10-08T21:48:17Z",
 "updated_at": "2026-10-25T07:43:22Z",
 "description_hash": "231a2159315f31bf",
 "age_days": 24,
 "draft": false,
 "labels": [
  "Wallet"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "4dbd27f37b52e49a8d392bd2ee6713ae7dcf23df",
 "head_ref": "broadcasttx-if-new",
 "head_repo": "achow101/bitcoin",
 "head_history": [],
 "additions": 163,
 "deletions": 65,
 "changed_files": 5,
 "commit_count": 5,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "polespinasa",
      "url": "https://github.com/bitcoin/bitcoin/pull/36482#issuecomment-6070254413"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "polespinasa": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-08T22:24:22Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 1,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "fametrano",
   "murchandamus",
   "mzumsande",
   "polespinasa"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-10-08T21:41:35Z",
  "last_reviewer_activity": "2026-10-25T07:43:22Z",
  "last_reviewer": "fametrano",
  "author_silent_days": 24,
  "waiting_on_author_days": 7,
  "days_since_update": 7
 },
 "refs": {
  "mentioned": [
   3828,
   36403
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 36403,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Rethinking Transaction Rebroadcasting"
   },
   {
    "number": 3828,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Clients leak IPs if they are recipients of a transaction"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "test/functional/wallet_resendwallettransactions.py"
 ],
 "body": "As discussed in #36403, one of the issues with current rebroadcast behavior is that the rebroadcast begins an unconditional relay of a transaction to peers. While this is broadcast is limited to peers that have not previously been inv'd the transaction, there is still a privacy leak in this double relay of a transaction, especially so for transactions that are still in nodes' mempools.\n\nThis PR changes the rebroadcast to only rebroadcast transactions if the transaction was not already present in our mempool. This provides some additional privacy while retaining intended rebroadcast behavior as transactions that have likely fallen out of nodes' mempools are the ones that actually need to be rebroadcast.\n\nThe wallet rebroadcast tests are additionally refactored for isolation so that their behaviors can still be tested after this change. Additionally, a test for the loading resubmission was added, as well as a test for the new behavior.",
 "commits": [
  {
   "sha": "2121d61323d2aad46d472e9a631d2fa0e574d111",
   "date": "2026-10-08T21:41:35Z",
   "message": "test: Refactor wallet_resendwallettransactions for test case isolation"
  },
  {
   "sha": "858be1c4477978af54d8db2d7f5f5f615d76b318",
   "date": "2026-10-08T21:41:35Z",
   "message": "test: Test wallet unconfirmed resubmission on load"
  },
  {
   "sha": "4446226ea9f835396c16fdfd388dd5e69c6174bc",
   "date": "2026-10-08T21:41:35Z",
   "message": "node: Add option to relay txs only if they are added to mempool\n\nBroadcastTransaction takes different modes of broadcasting. Add one\nwhich will attempt to add a transaction to the mempool, and if that\nsucceeds, broadcast the transaction to peers. If the transaction already\nexists in the mempool, it will not be rebroadcast."
  },
  {
   "sha": "8aed98fef4b43a914061475a4a1363286cdd86f7",
   "date": "2026-10-08T21:41:35Z",
   "message": "wallet: Only rebroadcast transactions that were re-added to the mempool"
  },
  {
   "sha": "4dbd27f37b52e49a8d392bd2ee6713ae7dcf23df",
   "date": "2026-10-08T21:41:35Z",
   "message": "test: Check that wallet txs already in the mempool are not rebroadcast"
  }
 ],
 "timeline": [
  {
   "t": "2026-10-08T22:24:22Z",
   "kind": "comment",
   "who": "polespinasa",
   "assoc": "MEMBER",
   "text": "concept ACK",
   "id": "c:6070254413",
   "url": "https://github.com/bitcoin/bitcoin/pull/36482#issuecomment-6070254413",
   "hash": "d0815209cf45d36b",
   "edited": null
  },
  {
   "t": "2026-10-09T15:32:09Z",
   "kind": "review_comment",
   "who": "mzumsande",
   "assoc": "MEMBER",
   "path": "test/functional/wallet_resendwallettransactions.py",
   "commit": "8aed98fef4b43a914061475a4a1363286cdd86f7",
   "in_reply_to": null,
   "text": "This replacement pattern with `indep_utxo` is just the mechanism that an attacker could do to probe for which nodes a given address belong to, so this doesn't appear to completely solve the receiver issue from #3828.",
   "id": "rc:4231801500",
   "url": "https://github.com/bitcoin/bitcoin/pull/36482#discussion_r4231801500",
   "hash": "d7de9e5905a247b9",
   "edited": null
  },
  {
   "t": "2026-10-21T18:25:38Z",
   "kind": "review",
   "who": "murchandamus",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "4dbd27f37b52e49a8d392bd2ee6713ae7dcf23df",
   "text": "This PR introduces the new `MEMPOOL_AND_BROADCAST_ADDED` TxBroadcast case, and uses it when the wallet resubmits transactions to the node\u2019s mempool instead of the transaction being rebroadcast to all peers that it hadn\u2019t been inventoried. The tests check that the rebroadcast happens when the transaction is added to the mempool and doesn\u2019t happen when the transaction was already present. This is an obvious privacy improvement.\n\nThe only open question I have at the moment is whether the combination of using `MEMPOOL_NO_BROADCAST` on wallet loading and then `MEMPOOL_AND_BROADCAST_ADDED` on the wallet\u2019s rebroadcast could prevent a transaction from being resubmitted to the network. E.g., if a wallet submits a low-feerate transaction, then the node is turned off for some period during which a feerate spike causes most of the network to drop the transaction, then the node is turned back on, wouldn\u2019t this mean that the wallet\u2019s transaction is not rebroadcast at all?\n\nAlso, I\u2019m not sure whether using `MEMPOOL_AND_BROADCAST_ADDED` during wallet loading would fix that, if the node still had the transaction in its mempool from before going offline.",
   "id": "r:5604335981",
   "url": "https://github.com/bitcoin/bitcoin/pull/36482#pullrequestreview-5604335981",
   "hash": "4d52f38660ab9db6",
   "edited": null
  },
  {
   "t": "2026-10-25T07:43:22Z",
   "kind": "comment",
   "who": "fametrano",
   "assoc": "CONTRIBUTOR",
   "text": "After a restart, this branch does not announce a wallet transaction that peers already received while our own mempool keeps it, so the answer to murch's question is yes.\n\nI built 4dbd27f37b and its base f739c7876b and ran a regtest script that connects a fresh P2PInterface peer, moves mocktime 36 hours ahead so that MaybeResendWalletTxs runs, and sends an unrelated transaction as a barrier. A wallet transaction still in the mempool is announced on base and not on the branch, as intended. One that expired and was accepted again is announced on both. One rejected as non-final is announced on neither.\n\nWith -persistmempool=0, the load-time resubmission puts the transaction back without broadcast. With mempool.dat, it comes back already out of the unbroadcast set, because a peer requested it before shutdown. The branch announced nothing in either case, and with -persistmempool=0 nothing over two periodic resubmissions and six ReattemptInitialBroadcast windows; base announced it at each resubmission. The same holds whenever our mempool keeps a transaction that peers dropped, until it expires from our mempool (two weeks by default). A node offline for days now takes its own mempool as a proxy for everyone else's. test_resubmit_on_load stops before the first periodic resubmission; extending it would pin whichever behaviour is intended.\n\nSmaller: the comment above ResubmitWalletTransactions still names MEMPOOL_AND_BROADCAST_TO_ALL for the periodic path, and \"resubmit N unconfirmed transactions\" now counts transactions not relayed. I can share the script as a gist.",
   "id": "c:6395009208",
   "url": "https://github.com/bitcoin/bitcoin/pull/36482#issuecomment-6395009208",
   "hash": "06f2f5060e7e49ef",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-08T21:48:22Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 4582,
 "text_tokens_estimate": 1145,
 "changed_paths": [
  "src/node/transaction.cpp",
  "src/node/types.h",
  "src/wallet/wallet.cpp",
  "test/functional/test_framework/p2p.py",
  "test/functional/wallet_resendwallettransactions.py"
 ],
 "files": [
  {
   "path": "src/node/transaction.cpp",
   "add": 9,
   "del": 1
  },
  {
   "path": "src/node/types.h",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/wallet/wallet.cpp",
   "add": 4,
   "del": 1
  },
  {
   "path": "test/functional/test_framework/p2p.py",
   "add": 7,
   "del": 0
  },
  {
   "path": "test/functional/wallet_resendwallettransactions.py",
   "add": 141,
   "del": 63
  }
 ],
 "test_lines": 211,
 "git": {
  "head": "4dbd27f37b52e49a8d392bd2ee6713ae7dcf23df",
  "head_matches_backup": true,
  "base": "f739c7876b81304bdc72e7eaa307702da269ac31",
  "patch_id": "fe2eadbba8148dc8",
  "commits": [
   {
    "sha": "2121d61323",
    "subject": "test: Refactor wallet_resendwallettransactions for test case isolation",
    "files": 1,
    "add": 70,
    "del": 58
   },
   {
    "sha": "858be1c447",
    "subject": "test: Test wallet unconfirmed resubmission on load",
    "files": 1,
    "add": 34,
    "del": 0
   },
   {
    "sha": "4446226ea9",
    "subject": "node: Add option to relay txs only if they are added to mempool",
    "files": 3,
    "add": 14,
    "del": 1
   },
   {
    "sha": "8aed98fef4",
    "subject": "wallet: Only rebroadcast transactions that were re-added to the mempool",
    "files": 2,
    "add": 21,
    "del": 5
   },
   {
    "sha": "4dbd27f37b",
    "subject": "test: Check that wallet txs already in the mempool are not rebroadcast",
    "files": 2,
    "add": 27,
    "del": 4
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "fe2eadbba8148dc8",
 "input_hash": "be6ff706ad98fe7c",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}