{
 "number": 34533,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/34533",
 "title": "wallet: resubmit transactions with private broadcast if enabled",
 "author": "vasild",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-02-07T05:35:17Z",
 "updated_at": "2026-08-19T01:30:09Z",
 "age_days": 222,
 "draft": true,
 "labels": [
  "Wallet",
  "Needs rebase",
  "Private Broadcast"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "38f47153253ba287071f88ce97d84036e5ea7872",
 "head_ref": "wallet_rebroadcast_use_private",
 "head_repo": "vasild/bitcoin",
 "head_history": [
  {
   "t": "2026-02-07T05:42:29Z",
   "sha": "a374c53b8d30ea60e56f07b45f0645f202109eff"
  },
  {
   "t": "2026-02-19T10:08:10Z",
   "sha": "16cd713073f45a5ddff1cdce73a7a8459bd8c618"
  },
  {
   "t": "2026-04-28T15:40:14Z",
   "sha": "38f47153253ba287071f88ce97d84036e5ea7872"
  }
 ],
 "additions": 54,
 "deletions": 11,
 "changed_files": 2,
 "commit_count": 2,
 "size_bucket": "S",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "pablomartin4btc",
      "url": "https://github.com/bitcoin/bitcoin/pull/34533#pullrequestreview-3767548873"
     }
    ],
    "stale_ack": [
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/34533#pullrequestreview-3829186494"
     },
     {
      "login": "andrewtoth",
      "url": "https://github.com/bitcoin/bitcoin/pull/34533#pullrequestreview-3851369970"
     }
    ]
   },
   "conflicts": [
    {
     "number": 34806,
     "title": "refactor: logging: Various API improvements",
     "author": "ajtowns"
    },
    {
     "number": 30343,
     "title": "wallet, logging: Replace WalletLogPrintf() with LogInfo()",
     "author": "ryanofsky"
    },
    {
     "number": 27865,
     "title": "wallet: Track no-longer-spendable TXOs separately",
     "author": "achow101"
    },
    {
     "number": 19461,
     "title": "multiprocess: Add bitcoin-gui -ipcconnect option",
     "author": "ryanofsky"
    },
    {
     "number": 19460,
     "title": "multiprocess: Add bitcoin-wallet -ipcconnect option",
     "author": "ryanofsky"
    },
    {
     "number": 10102,
     "title": "Multiprocess bitcoin",
     "author": "ryanofsky"
    }
   ]
  }
 },
 "acks_parsed": {
  "pablomartin4btc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-02-07T18:13:05Z",
   "stale": false
  },
  "w0xlt": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-02-08T04:55:36Z",
   "stale": false
  },
  "andrewtoth": {
   "kind": "ack",
   "hash": "16cd713073f45a5ddff1cdce73a7a8459bd8c618",
   "t": "2026-02-25T01:40:11Z",
   "stale": true
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 1,
  "concept_ack": 2,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 1,
  "changes_requested": 0,
  "distinct_reviewers": [
   "achow101",
   "andrewtoth",
   "instagibbs",
   "mzumsande",
   "pablomartin4btc",
   "sedited",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": false,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-04-28T15:40:48Z",
  "last_reviewer_activity": "2026-05-11T14:45:59Z",
  "last_reviewer": "instagibbs",
  "author_silent_days": 142,
  "waiting_on_author_days": 129,
  "days_since_update": 29
 },
 "refs": {
  "mentioned": [
   3828,
   29415
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 3828,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Clients leak IPs if they are recipients of a transaction"
   },
   {
    "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"
   }
  ],
  "conflicts": [
   34806,
   30343,
   27865,
   19461,
   19460,
   10102
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/wallet/wallet.cpp"
 ],
 "body": "The wallet keeps track of transactions related to it and periodically rebroadcasts them every 12-36 hours if they are not mined.\n\nIf `-privatebroadcast=1` and a transaction is submitted locally with the `sendrawtransaction` RPC and it is related to the wallet, then the rebroadcasts would use the send-to-all method. Change that to use the private broadcast method.",
 "commits": [
  {
   "sha": "fe66f0bb3d9e0463f45f2fde340dc2cc4c47f463",
   "date": "2026-04-28T06:34:12Z",
   "message": "wallet: improve log message\n\nThe log message \"Submitting wtx %s\" might be misleading because the\nprinted value is the txid, not wtxid.\n\nChange that to print both: \"txid=%s wtxid=%s\".\n\nAlso, include the outcome of the submit which is useful for the one\ncaller that does not print the error string."
  },
  {
   "sha": "38f47153253ba287071f88ce97d84036e5ea7872",
   "date": "2026-04-28T15:37:59Z",
   "message": "wallet: resubmit transactions with private broadcast if enabled\n\nThe wallet keeps track of transactions related to it and periodically\nrebroadcasts them every 12-36 hours if they are not mined.\n\nIf `-privatebroadcast=1` and a transaction is submitted locally with the\n`sendrawtransaction` RPC and it is related to the wallet, then the\nrebroadcasts would use the send-to-all method. Change that to use the\nprivate broadcast method."
  }
 ],
 "timeline": [
  {
   "t": "2026-02-07T05:38:34Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "38f47153253ba287071f88ce97d84036e5ea7872",
   "in_reply_to": null,
   "text": "This is just for the constant `DEFAULT_PRIVATE_BROADCAST`. Is there a better place for it? It is used in the following files:\n\n```\nsrc/init.cpp\nsrc/net.cpp\nsrc/net_processing.h\nsrc/rpc/mempool.cpp\nsrc/wallet/wallet.cpp (new in this PR)\n```"
  },
  {
   "t": "2026-02-07T05:42:29Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "a374c53b8d30ea60e56f07b45f0645f202109eff"
  },
  {
   "t": "2026-02-07T18:13:05Z",
   "kind": "review",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "a374c53b8d30ea60e56f07b45f0645f202109eff",
   "text": "Concept ACK.\n\nThis is necessary to support the private transaction broadcast mechanism introduced in #29415 when transactions are rebroadcast or resubmitted, which does not seem to be handled currently."
  },
  {
   "t": "2026-02-08T04:55:36Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK \u2014 though I am not sure this is the best long-term approach. However, it could work as a pragmatic short-term solution if we need something quickly.\n\nOne concern with the approach here is that if a user uses `sendrawtransaction` to submit a wallet-related transaction while `-privatebroadcast` is enabled and then restarts the node without `-privatebroadcast`, the transaction would be rebroadcast via mempool since there is no per-transaction tracking of how it was originally sent.\n\nThis is being discussed further here: https://github.com/bitcoin/bitcoin/pull/34457#issuecomment-3864095203. As mentioned there, a possible alternative could be for the wallet to treat all `IsFromMe()` transactions not originated through `CommitTransaction()` as private by default (that PR introduces a `private` flag in `CWalletTx::mapValue`) and only allow rebroadcasting when `-privatebroadcast` is enabled."
  },
  {
   "t": "2026-02-12T12:58:30Z",
   "kind": "review",
   "who": "mzumsande",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "a374c53b8d30ea60e56f07b45f0645f202109eff",
   "text": "[quoted text omitted]\n\nAn attacker always has the alternative option to just create their own tx to dust the address - this would circumvent any transaction tracking, and would be more convenient to the attacker anyway because this way the attacker can choose the fee carefully - low enough to make rebroadcast very probable, and high enough to make mempool eviction unprobable.\nThis PR fixes this problem for both `IsFromMe()` and `IsMine()` in a way transaction tracking wouldn't. However, I think the problem is concerning enough that it should also be solved for clearnet-only nodes (#3828)\n\n[quoted text omitted]\nAs argued above, I think this is not complete unless we also do something about `IsMine()` transactions."
  },
  {
   "t": "2026-02-13T09:36:33Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nWhy? I think wallet support for private broadcast includes this change, which only relates to re-broadcasts. I do not see this as a short-term solution. The rest of the wallet support, for transactions initiated from the wallet (I guess in https://github.com/bitcoin/bitcoin/pull/34457, I did not study it yet, but will) goes together with this.\n\n[quoted text omitted]\nHmm, as a user, this is what I would expect - if restarted with `-privatebroadcast=0`, then the wallet rebroadcasts should be done using the send-to-all method (\"via the mempool\"). I would expect that because I have switched off private broadcast."
  },
  {
   "t": "2026-02-19T10:08:10Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "16cd713073f45a5ddff1cdce73a7a8459bd8c618"
  },
  {
   "t": "2026-02-19T10:08:44Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`a374c53b8d30ea60e56f07b45f0645f202109eff...16cd713073f45a5ddff1cdce73a7a8459bd8c618`: rebase due to conflicts"
  },
  {
   "t": "2026-02-20T00:18:01Z",
   "kind": "review",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "16cd713073f45a5ddff1cdce73a7a8459bd8c618",
   "text": "[quoted text omitted]\n\nIn the other PR, I originally proposed tracking privately broadcast transactions in the wallet and only rebroadcasting them when `-privatebroadcast` is enabled again.\n\nWhile this works, it adds state complexity (for example, if the user never restarts the node with `-privatebroadcast`, the transaction would never be rebroadcast).\n\nThe main motivation was to prevent unaware users from having their originally privately broadcast transactions later rebroadcast differently if the node is restarted without `-privatebroadcast`, since rebroadcasting is not a deliberate user action. However, it\u2019s probably better to keep things simple.\n\nACK 16cd713073f45a5ddff1cdce73a7a8459bd8c618"
  },
  {
   "t": "2026-02-25T01:40:11Z",
   "kind": "review",
   "who": "andrewtoth",
   "assoc": "CONTRIBUTOR",
   "state": "APPROVED",
   "commit": "16cd713073f45a5ddff1cdce73a7a8459bd8c618",
   "text": "ACK 16cd713073f45a5ddff1cdce73a7a8459bd8c618"
  },
  {
   "t": "2026-02-27T00:15:59Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "Disclaimer: I have not reviewed the private broadcast code yet, so some of these concerns may not make sense.\n\nI'm not convinced that this is ready for the wallet until there is support for private broadcasting of packages, and for the wallet to submit packages when rebroadcasting.\n\nSpecifically, the wallet may contain transactions that have ancestors and descendants, and these may be complex topologies. Now, the rebroadcasting logic is quite naive as it sends all of the unconfirmed transactions into the mempool one by one, which means that if each transaction could not be accepted on its own, it cannot get rebroadcast. The wallet does at least submit them in the correct order so that transactions that depend on other transactions in the wallet won't be rejected for missing-inputs, unless an ancestor was rejected for some other reason.\n\nBut, I believe this is made worse if private broadcast is enabled. AFAICT, each private broadcast sends the transaction to a random node. This means that if there is a dependency, a child transaction is unlikely to be sent to the node that the parent was sent to, which means that it is unlikely to be accepted by the node it is sent to due to the parent being unknown. Although looking at the code, such a scenario would result in the child never being attempted to be rebroadcast as it would fail `ProcessTransaction(test_accept=true)` since the parent is not in the local mempool. At best this delays the rebroadcast of the child for up to 24 hours, at worst, the child can never get rebroadcast.\n\nHere's a specific scenario that I think would be problematic. Consider a typical CPFP - a low feerate parent and a high feerate child. Suppose that these happen to not be in the mempool for whatever reason and the wallet is trying to rebroadcast them.\n\nWithout private broadcast, the wallet will send both to the local mempool individually, first the parent, then the child. If the parent cannot be accepted, then the child won't either and nothing gets broadcast. If the parent is accepted, then the child will be accepted as well and both transactions can get relayed as normal, and may even benefit from opportunistic 1p1c.\n\nWith private broadcast, first the parent is tested against the local mempool, then the child. If the parent would not be accepted, then neither get broadcast and it's the same situation as with normal relay. But if the parent would be accepted, it gets queued to be sent off to a random peer. Then the child gets tested against the local mempool, and it fails because the parent is not in the local mempool. Once the parent gets relayed and it shows up in the local mempool, if it's still there up to 24 hours later, the child gets broadcast and now it's the normal relay scenario, just 24 hours later.\n\nBut, suppose the parent got relayed, but it falls out of the mempool before the next rebroadcast (highly dynamic fee environment, with the parent sitting just around minrelayfeerate). Now we're back to square one and the parent has to get relayed again. Theoretically, this could cycle indefinitely and the transactions never get mined."
  },
  {
   "t": "2026-03-04T14:59:14Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nCorrect. Possible solutions to that:\n\n* Send the parent and the child via the same private broadcast connection (package private broadcast); or\n* Wait for the parent to round-trip through the network to our mempool (usually a few seconds) and only then private broadcast the child to another random peer (soon, not 24h later)."
  },
  {
   "t": "2026-03-04T15:14:50Z",
   "kind": "comment",
   "who": "andrewtoth",
   "assoc": "CONTRIBUTOR",
   "text": "I believe https://github.com/bitcoin/bitcoin/pull/34707 fixes this CPFP issue. If both txs are submitted to private broadcast, then when the stale tx check occurs it will mark the child as mempool-rejected but not remove it from the queue. Once the parent makes it back into the mempool, the stale tx check will see that the child is now acceptable and begin broadcasting it again."
  },
  {
   "t": "2026-03-11T08:48:13Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nShould this PR be drafted while those things are being worked on?"
  },
  {
   "t": "2026-04-28T15:40:14Z",
   "kind": "force_push",
   "who": "vasild",
   "commit": "38f47153253ba287071f88ce97d84036e5ea7872"
  },
  {
   "t": "2026-04-28T15:40:48Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "CONTRIBUTOR",
   "text": "`16cd713073f45a5ddff1cdce73a7a8459bd8c618...38f47153253ba287071f88ce97d84036e5ea7872`: rebase due to conflicts"
  },
  {
   "t": "2026-05-11T14:45:59Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nNote that we can only \"fix\" this up to our package relay support limits, aka 1P1C."
  }
 ],
 "labels_log": [
  {
   "t": "2026-02-07T05:35:20Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-07T05:42:56Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-09T08:23:53Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-18T19:30:56Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-19T11:07:17Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-19T12:03:38Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-20T08:47:33Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-11T11:16:14Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-21T13:55:47Z",
   "action": "labeled",
   "label": "Private Broadcast",
   "who": "fanquake"
  },
  {
   "t": "2026-04-28T15:57:45Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-22T04:12:41Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-02-20T07:41:39Z",
   "kind": "closed",
   "who": "maflcko"
  },
  {
   "t": "2026-02-20T07:41:48Z",
   "kind": "reopened",
   "who": "maflcko"
  },
  {
   "t": "2026-03-11T09:37:46Z",
   "kind": "convert_to_draft",
   "who": "fanquake"
  }
 ],
 "text_chars": 8890,
 "text_tokens_estimate": 2222,
 "changed_paths": [],
 "files": [],
 "test_lines": null,
 "git": null,
 "input_hash": "1f075871b49febfc",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}