{
 "number": 36398,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36398",
 "title": "wallet: only rebroadcast transactions originating from the wallet",
 "author": "mzumsande",
 "author_association": "MEMBER",
 "created_at": "2026-10-01T14:25:46Z",
 "updated_at": "2026-10-25T11:23:34Z",
 "description_hash": "3c283b685147bf1a",
 "age_days": 31,
 "draft": false,
 "labels": [
  "Wallet"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "f63a38f781ad91c2047a1bdf5112477e87b6531d",
 "head_ref": "202620_wallet_restric_rebroadcast",
 "head_repo": "mzumsande/bitcoin",
 "head_history": [
  {
   "t": "2026-10-01T14:26:45Z",
   "sha": "f63a38f781ad91c2047a1bdf5112477e87b6531d"
  }
 ],
 "additions": 28,
 "deletions": 6,
 "changed_files": 3,
 "commit_count": 2,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_nack": [
     {
      "login": "achow101",
      "url": "https://github.com/bitcoin/bitcoin/pull/36398#issuecomment-5936779475"
     }
    ],
    "concept_ack": [
     {
      "login": "vasild",
      "url": "https://github.com/bitcoin/bitcoin/pull/36398#issuecomment-6012406070"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "achow101": {
   "kind": "concept_nack",
   "hash": null,
   "t": "2026-10-01T17:25:00Z",
   "stale": false
  },
  "vasild": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-06T08:27:45Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 1,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 1,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "achow101",
   "fametrano",
   "instagibbs",
   "vasild"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-10-01T14:26:45Z",
  "last_reviewer_activity": "2026-10-25T11:23:34Z",
  "last_reviewer": "fametrano",
  "author_silent_days": 31,
  "waiting_on_author_days": 7,
  "days_since_update": 7
 },
 "refs": {
  "mentioned": [
   3828,
   36403
  ],
  "depends_on": [],
  "fixes": [
   3828
  ],
  "linked_issues": [
   {
    "number": 3828,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Clients leak IPs if they are recipients of a transaction"
   }
  ],
  "references": [
   {
    "number": 3828,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Clients leak IPs if they are recipients of a transaction"
   },
   {
    "number": 36403,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Rethinking Transaction Rebroadcasting"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "As explained in #3828, rebroadcast of transactions paying the wallet (but not spending any wallet inputs) is a privacy leak, since anyone can generate these, choose a low fee, and observe who rebroadcasts them.\nWhile it is somewhat intuitive that submitting transactions has privacy implications - at least until confirmation - this is manageable by various strategies (switching to privacy networks for a while, using private broadcast, making sure it confirms before rebroadcast). However, I find it rather unexpected that just passively running a node with a wallet loaded can lead to a loss of privacy as well.\n\nThis PR stops rebroadcasting transactions that don't spend any wallet inputs. For the same reason, they are also no longer re-added to the mempool on startup or after an import: otherwise our node might be the only one still having a transaction the rest of the network has dropped, which can be detected with active probing (e.g. by submitting a child).\n\nThis does have the trade-off that receivers (who have an incentive to get paid) will now need to manually resubmit transactions if the transaction doesn't confirm and the sender doesn't rebroadcast, but I think it is worth the privacy benefit.\n\nAn alternative approach is https://github.com/bitcoin/bitcoin/pull/36380, although these could also be combined (make rebroadcast optional, but never rebroadcast non-originating transactions)\n\nFixes #3828",
 "commits": [
  {
   "sha": "23813b6ad5d618c67e6bfbe5217b2006b7de8f49",
   "date": "2026-10-01T14:06:59Z",
   "message": "wallet: only rebroadcast transactions originating from the wallet\n\nRebroadcasting transactions that only pay the wallet is a privacy leak,\nsince anyone can send low-fee transactions to a wallet, unlikely to be\nmined in 24h, and observe who rebroadcasts them. Also don't re-add them\nto the mempool in case of a restart if they were previously dropped, to\nprevent active probing.\n\nThis means that rebroadcasting is now the sole responsibility of the\nsender."
  },
  {
   "sha": "f63a38f781ad91c2047a1bdf5112477e87b6531d",
   "date": "2026-10-01T14:26:13Z",
   "message": "doc: add release note for wallet rebroadcast change"
  }
 ],
 "timeline": [
  {
   "t": "2026-10-01T14:26:45Z",
   "kind": "force_push",
   "who": "mzumsande",
   "commit": "f63a38f781ad91c2047a1bdf5112477e87b6531d",
   "id": "push:f63a38f781ad91c2047a1bdf5112477e87b6531d"
  },
  {
   "t": "2026-10-01T17:25:00Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "Concept NACK-ish\n\nRebroadcasting transactions that are not ours is useful as rebroadcasting does have an effect on getting such transactions confirmed. Furthermore, if we want to CPFP a transaction, it needs to be in our mempool first, otherwise the wallet will be unable to broadcast the child.\n\nInstead of trying to delete rebroadcasting in the name of privacy, we should consider other ways to enable rebroadcasting in a privacy preserving manner, such as a rebroadcast pool in the node.",
   "id": "c:5936779475",
   "url": "https://github.com/bitcoin/bitcoin/pull/36398#issuecomment-5936779475",
   "hash": "1929f3544cdedaf4",
   "edited": null
  },
  {
   "t": "2026-10-01T17:51:09Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "rebroadcast pool histories: https://github.com/bitcoin/bitcoin/pull/16698 and https://github.com/bitcoin/bitcoin/pull/21061 in case its helpful\n\nI think we need to start a fresh issue on the topic and lay down alternatives side by side",
   "id": "c:5937194951",
   "url": "https://github.com/bitcoin/bitcoin/pull/36398#issuecomment-5937194951",
   "hash": "06f2ad16a4abe8fc",
   "edited": null
  },
  {
   "t": "2026-10-01T19:49:35Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "I've opened #36403 where we can have more high level discussion about transaction rebroadcast in general.",
   "id": "c:5939298900",
   "url": "https://github.com/bitcoin/bitcoin/pull/36398#issuecomment-5939298900",
   "hash": "92e45907e178e160",
   "edited": null
  },
  {
   "t": "2026-10-06T08:27:45Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "text": "Concept ACK, but make this configurable somehow.\n\nLike somebody said \"there is no one size fits all\" and from [above](https://github.com/bitcoin/bitcoin/pull/36398#issuecomment-5936779475) - rebroadcasting by the recipient has some positive sides.\n\nIn such cases I think it is best if we let the users decide what is best for them - give them the tools to configure it according to their environment and priorities.\n\nIf you decide to make this configurable, one possibility would be to extend `-walletbroadcast=<0|1>` with a new value, similarly to https://github.com/bitcoin/bitcoin/pull/36380.",
   "id": "c:6012406070",
   "url": "https://github.com/bitcoin/bitcoin/pull/36398#issuecomment-6012406070",
   "hash": "ff2cd026e6590f02",
   "edited": null
  },
  {
   "t": "2026-10-25T11:23:34Z",
   "kind": "comment",
   "who": "fametrano",
   "assoc": "CONTRIBUTOR",
   "text": "After a restart without mempool.dat, or after mempool expiry, a CPFP child that the wallet itself created for an incoming payment stays out of the mempool on this branch, and no resend timer brings it back.\n\nThe child spends our output of the incoming parent, so `IsFromMe()` still selects it, but the parent is now skipped, and without the parent the child cannot enter the mempool. I measured this on regtest at f63a38f781 and at its base d4b0e1e4b0: one node pays the wallet at 1 sat/vB and is disconnected, then the wallet spends that unconfirmed output at 50 sat/vB. After a restart with `-persistmempool=0`, the base re-adds both transactions and announces both to a fresh peer at the next resend timer; this branch re-adds neither and announces neither. Letting both expire from the mempool, without a restart, gives the same result. Submitting the parent by hand with `sendrawtransaction`, as the release note suggests, brings the child back at the next timer.\n\nachow101 noted above that CPFP needs the parent in our mempool; this is the same dependency once the child already exists. One way to keep it would be to also resubmit an unconfirmed incoming parent whose output a resubmitted transaction of ours spends: announcing that child already shows that the parent pays us. Otherwise the release note could name this case. I can share the probe script.",
   "id": "c:6397012438",
   "url": "https://github.com/bitcoin/bitcoin/pull/36398#issuecomment-6397012438",
   "hash": "72382ea1f7bbae1d",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-01T14:25:50Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-01T14:27:55Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-01T15:54:55Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 4788,
 "text_tokens_estimate": 1197,
 "changed_paths": [
  "doc/release-notes-36398.md",
  "src/wallet/wallet.cpp",
  "test/functional/wallet_resendwallettransactions.py"
 ],
 "files": [
  {
   "path": "doc/release-notes-36398.md",
   "add": 8,
   "del": 0
  },
  {
   "path": "src/wallet/wallet.cpp",
   "add": 10,
   "del": 2
  },
  {
   "path": "test/functional/wallet_resendwallettransactions.py",
   "add": 10,
   "del": 4
  }
 ],
 "test_lines": 14,
 "git": {
  "head": "f63a38f781ad91c2047a1bdf5112477e87b6531d",
  "head_matches_backup": true,
  "base": "d4b0e1e4b078dbb4ab57886d288470684dec5718",
  "patch_id": "e20c703eb71b0c1f",
  "commits": [
   {
    "sha": "23813b6ad5",
    "subject": "wallet: only rebroadcast transactions originating from the wallet",
    "files": 2,
    "add": 20,
    "del": 6
   },
   {
    "sha": "f63a38f781",
    "subject": "doc: add release note for wallet rebroadcast change",
    "files": 1,
    "add": 8,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "e20c703eb71b0c1f",
 "input_hash": "ef157a9ab6db3015",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}