{
 "number": 36566,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36566",
 "title": "private broadcast: wait synchronously for mempool acceptance",
 "author": "andrewtoth",
 "author_association": "MEMBER",
 "created_at": "2026-10-18T17:57:53Z",
 "updated_at": "2026-10-30T19:49:47Z",
 "description_hash": "18968d0c8a61540a",
 "age_days": 14,
 "draft": false,
 "labels": [
  "Private Broadcast"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "bccae015158c741ac16257ffccdc605fe1f49168",
 "head_ref": "private-broadcast-wait",
 "head_repo": "andrewtoth/bitcoin",
 "head_history": [
  {
   "t": "2026-10-18T18:14:23Z",
   "sha": "bccae015158c741ac16257ffccdc605fe1f49168"
  }
 ],
 "additions": 259,
 "deletions": 42,
 "changed_files": 17,
 "commit_count": 4,
 "size_bucket": "M",
 "mergeable_state": "unstable",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "conflicts": []
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "instagibbs"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "unstable",
  "last_author_activity": "2026-10-18T18:14:23Z",
  "last_reviewer_activity": "2026-10-21T17:22:09Z",
  "last_reviewer": "instagibbs",
  "author_silent_days": 14,
  "waiting_on_author_days": 11,
  "days_since_update": 2
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "Calling `BroadcastTransaction` with the `NO_MEMPOOL_PRIVATE_BROADCAST` method skips inserting the transaction into the mempool and returns immediately. This causes problems with software that expects the transaction to be in the mempool after the function returns. For example many third-party wallets or services call `sendrawtransaction` and expect the transaction to be in the mempool after it returns a successful response.\n\nThis PR adds a new `TransactionAddedToMempoolSync` method to `CValidationInterface`. This is then used in `BroadcastTransaction` when the method is `NO_MEMPOOL_PRIVATE_BROADCAST` to wait for the transaction to have been accepted back into the mempool before returning. It also adds a timeout (default 10 minutes) to `BroadcastTransaction` and exposes it as an option for `sendrawtransaction`. Setting the timeout to 0 disables the wait and returns immediately, preserving the current behavior.\n\nSynchronously waiting for mempool acceptance is also a prerequisite for adding private broadcast support to the wallet. The `TransactionAddedToMempoolSync` method is added specifically for future wallet broadcasting, so a broadcast caller holding `cs_wallet` will not timeout waiting on another background event that is also trying to acquire `cs_wallet`.",
 "commits": [
  {
   "sha": "9e7e5dcf784163b16a74346971f4f36a6635f126",
   "date": "2026-10-18T17:29:05Z",
   "message": "validation: add synchronous mempool acceptance notification\n\nNotify validation subscribers synchronously after queuing the existing mempool acceptance event. This allows acceptance waiters to wake independently of the background notification queue."
  },
  {
   "sha": "8f89a7a95a7aa48d10b32742a6a5ddca6ae59b49",
   "date": "2026-10-18T17:29:05Z",
   "message": "node: support waiting for private broadcast mempool acceptance\n\nSubscribe before private broadcast submission and wait for local mempool acceptance, timeout, or shutdown. Synchronous acceptance notifications allow callers holding the wallet lock to wait without draining its notification queue."
  },
  {
   "sha": "9f3c95e8c4549f39023287ac288c42704c1e4af9",
   "date": "2026-10-18T18:14:13Z",
   "message": "rpc: wait for private broadcast acceptance in sendrawtransaction\n\nExpose the private broadcast timeout in sendrawtransaction and default private broadcasts to a ten-minute wait."
  },
  {
   "sha": "bccae015158c741ac16257ffccdc605fe1f49168",
   "date": "2026-10-18T18:14:13Z",
   "message": "doc: add release notes for private broadcast wait"
  }
 ],
 "timeline": [
  {
   "t": "2026-10-18T18:14:23Z",
   "kind": "force_push",
   "who": "andrewtoth",
   "commit": "bccae015158c741ac16257ffccdc605fe1f49168",
   "id": "push:bccae015158c741ac16257ffccdc605fe1f49168"
  },
  {
   "t": "2026-10-21T17:22:09Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nCan we gather a list of these?\n\nGiven the Core wallet motivation in part I think I'd like to see end to end wallet integration before stepping through this change. Naively done, it could end up with some dangerous behavior for users, such as:\n\n - sendtoaddress hangs while Tor connects and the transaction travels out and back. Waits up to 10 minutes(?)\n - The user presses Ctrl-C. The transaction is already in the wallet and queued.\n - The user runs getbalance or listtransactions to check what happened. Both block on cs_wallet, so the user can't see that the first send went through. I think they'd have to inspect the private broadcast queue instead to know?\n - The user runs sendtoaddress again. It blocks on cs_wallet until the first call finishes. Then coin selection skips the now-spent inputs and builds a second transaction from other coins. Both get broadcast, and the user pays twice.\n\nsendtoaddress was never idempotent to begin with, which is kind of scary on its own, I just think this broadens the scope of possible loss through unclear UX. Users suddenly \"losing\" funds by stuff not being in mempool and not being accounted for will at least cause the users to slow down :/",
   "id": "c:6322161954",
   "url": "https://github.com/bitcoin/bitcoin/pull/36566#issuecomment-6322161954",
   "hash": "90e8fccc65560314",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-19T03:27:52Z",
   "action": "labeled",
   "label": "Private Broadcast",
   "who": "fanquake"
  }
 ],
 "state_log": [],
 "text_chars": 3327,
 "text_tokens_estimate": 831,
 "changed_paths": [
  "doc/release-notes-private-broadcast-wait.md",
  "src/common/messages.cpp",
  "src/node/transaction.cpp",
  "src/node/transaction.h",
  "src/node/types.h",
  "src/rpc/client.cpp",
  "src/rpc/mempool.cpp",
  "src/rpc/util.cpp",
  "src/test/validationinterface_tests.cpp",
  "src/validationinterface.cpp",
  "src/validationinterface.h",
  "test/functional/p2p_private_broadcast.py",
  "test/functional/p2p_private_broadcast_cap.py",
  "test/functional/p2p_private_broadcast_completion.py",
  "test/functional/p2p_private_broadcast_retry_v1.py",
  "test/functional/p2p_private_broadcast_wait.py",
  "test/functional/test_runner.py"
 ],
 "files": [
  {
   "path": "doc/release-notes-private-broadcast-wait.md",
   "add": 10,
   "del": 0
  },
  {
   "path": "src/common/messages.cpp",
   "add": 4,
   "del": 0
  },
  {
   "path": "src/node/transaction.cpp",
   "add": 63,
   "del": 24
  },
  {
   "path": "src/node/transaction.h",
   "add": 11,
   "del": 1
  },
  {
   "path": "src/node/types.h",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/rpc/client.cpp",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/rpc/mempool.cpp",
   "add": 8,
   "del": 3
  },
  {
   "path": "src/rpc/util.cpp",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/test/validationinterface_tests.cpp",
   "add": 79,
   "del": 1
  },
  {
   "path": "src/validationinterface.cpp",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/validationinterface.h",
   "add": 5,
   "del": 0
  },
  {
   "path": "test/functional/p2p_private_broadcast.py",
   "add": 7,
   "del": 7
  },
  {
   "path": "test/functional/p2p_private_broadcast_cap.py",
   "add": 3,
   "del": 3
  },
  {
   "path": "test/functional/p2p_private_broadcast_completion.py",
   "add": 2,
   "del": 2
  },
  {
   "path": "test/functional/p2p_private_broadcast_retry_v1.py",
   "add": 1,
   "del": 1
  },
  {
   "path": "test/functional/p2p_private_broadcast_wait.py",
   "add": 59,
   "del": 0
  },
  {
   "path": "test/functional/test_runner.py",
   "add": 1,
   "del": 0
  }
 ],
 "test_lines": 166,
 "git": {
  "head": "bccae015158c741ac16257ffccdc605fe1f49168",
  "head_matches_backup": true,
  "base": "924f4c4ac78b0cd3b917c694f42c0651a6f3003a",
  "patch_id": "aeb97d2e587991da",
  "commits": [
   {
    "sha": "9e7e5dcf78",
    "subject": "validation: add synchronous mempool acceptance notification",
    "files": 2,
    "add": 6,
    "del": 0
   },
   {
    "sha": "8f89a7a95a",
    "subject": "node: support waiting for private broadcast mempool acceptance",
    "files": 6,
    "add": 161,
    "del": 26
   },
   {
    "sha": "9f3c95e8c4",
    "subject": "rpc: wait for private broadcast acceptance in sendrawtransaction",
    "files": 9,
    "add": 83,
    "del": 17
   },
   {
    "sha": "bccae01515",
    "subject": "doc: add release notes for private broadcast wait",
    "files": 1,
    "add": 10,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "aeb97d2e587991da",
 "input_hash": "c766dc4c0b94e050",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}