private broadcast: Extra guard against unintended non-private broadcast of private transactions #36607

pull optout21 wants to merge 4 commits into bitcoin:master from optout21:2610-privbcast-guard changing 13 files +502 −78
  1. optout21 commented at 2:03 PM on October 22, 2026: contributor

    Motivation: Reduce the risk of accidental privacy leak happening when broadcasting non-privately a requested transaction that had been previously broadcast privately.

    Background: During the initial phase of private broadcast implementation there were unforeseen instances when a private transaction could get broadcast non-privately (see #35032 #35319 #35410 #36309 #36312 #36277 #36330).

    Currently known pathways:

    • Re-submission through submitpackage
    • Wallet-rebroadcast (when a private transaction is "picked up" by the wallet, and rebrodcast after 1-2 days)

    Note that these are being addressed, but other, currently non-identified pathways cannot be excluded.

    Change summary: Add a general, extra safeguard: whenever there is a transaction broadcast request, check if the given transaction has been previously broadcast privately, and if so, refuse to broadcast non-privately.

    Transaction relay is not affected, as it should not be affected by private broadcast. When we receive a previously private-broadcast transaction for the first time (it's not yet in our mempool), we relay it as any other relayed transaction. Behavior is unchanged in this regard.

    Notes:

    • Prerequisite: This is built on top of #36477, as keeping finished private broadcasts in the queue is a prerequisite for this change to work.
    • This proposed change is not an alternative to the use-case specific safeguards, but a complementary one.
    • Testing currently uses the two pathways listed above. When they are closed, testing will be more difficult (i.e., it is hard to establish whether non-private broadcast is prevented by the use-case specific or the present generic guard).
    • The private broadcast queue is not persisted, it doesn't survive node restarts
    • Entries deleted via abortprivatebroadcast will be forgotten.

    Keeping as Draft, because the prerequisite #36477 is pending.

  2. DrahtBot added the label Private Broadcast on Oct 22, 2026
  3. DrahtBot commented at 2:03 PM on October 22, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process. A summary of reviews will appear here.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36628 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36628.svg"></sub> (net: reimplement private broadcast as jobs isolated from the node by instagibbs)
    • #36608 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36608.svg"></sub> (Reimplement private broadcast as node state-less jobs + package relay support by instagibbs)
    • #36597 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36597.svg"></sub> (wallet: return broadcast errors from CommitTransaction by pseudoramdom)
    • #36566 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36566.svg"></sub> (private broadcast: wait synchronously for mempool acceptance by andrewtoth)
    • #36477 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36477.svg"></sub> (private broadcast: show finished transactions in getprivatebroadcastinfo by vasild)
    • #36429 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36429.svg"></sub> ([RFC] Delayed transaction broadcast, minimal by optout21)
    • #34824 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/34824.svg"></sub> (net: encapsulate TxRelay state and replace recursive mutexes by w0xlt)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • test/functional/p2p_private_broadcast_then_public.py: privateproadcast-aware → private-broadcast-aware [misspelling]

    <sup>2026-10-28 09:40:56</sup>

  4. DrahtBot added the label CI failed on Oct 22, 2026
  5. private broadcast: show finished transactions in getprivatebroadcastinfo
    For finished transactions, this allows the inspection of when and to
    whom the transaction was sent as well as when/if it was echoed from
    the network.
    
    Co-authored-by: Vasil Dimov <vd@FreeBSD.org>
    295c961c1f
  6. test: Add new private-then-public broadcast test via submitpackage 7f31ea576c
  7. optout21 force-pushed on Oct 28, 2026
  8. optout21 commented at 7:42 AM on October 28, 2026: contributor

    CI failure: TSan fails. It may be related to the test pushing the mock time by 36 hours, which may affect some rate limiting. Investigating.

  9. optout21 commented at 8:16 AM on October 28, 2026: contributor

    Attempted fix for "TSan" CI timeout.

    Likely root cause: In the wallet resend part, node0's mocktime jumps 36 hours and then stays frozen. If node0's message handler processes the nodes[2] connection between the jump and the wallet resubmit, it resets that peer's inv trickle timer to the frozen time plus a random delay, so the timer never expires and the inv for the resent transaction is never sent.

    Attempted fix: start node0 with -whitelist=noban,out@127.0.0.1, which gives its manual connections noban permission so they skip the trickle timer, as wallet_resendwallettransactions.py does via noban_tx_relay.

    git range-diff 7b2aae41d79624abc87198932d699ae77ec36323...0cd792580f01796d0ed16d9cf415f7b8a6ac5f20

  10. optout21 force-pushed on Oct 28, 2026
  11. maflcko commented at 8:37 AM on October 28, 2026: member

    Attempted fix: start node0 with -whitelist=noban,out@127.0.0.1, which gives its manual connections noban permission so they skip the trickle timer, as wallet_resendwallettransactions.py does via noban_tx_relay.

    Haven't looked closely, but I wonder if this is the correct fix here. wallet_resendwallettransactions.py only cares about the day-long resend-delay, not the second-long trickle-delay, so it seems correct for that test. However, for the test here, either the test here cares about trickle-delay, in which case it would be cleaner to not use noban permission and instead make the test deterministic with syncs or so? Or, the test doesn't care about trickle delay, and can just set noban_tx_relay globally? Just wondering, up to you.

  12. DrahtBot removed the label CI failed on Oct 28, 2026
  13. test: Add private-then-public broadcast test via wallet resend
    Extend p2p_private_broadcast_then_public.py with a second case (if the
    wallet is compiled): a wallet transaction is sent via private broadcast,
    comes back from the network into the mempool and the wallet of the
    originator, and is then announced to all peers by the periodic resend
    of the wallet.
    ca6fdbe4a8
  14. net_processing: don't announce privately broadcast txs on request
    With -privatebroadcast, sendrawtransaction sends the transaction only
    via short-lived private broadcast connections and does not add it to
    the mempool. However, other code paths can later add the same
    transaction to the mempool and announce it to all peers, which links
    the transaction to this node:
    - the submitpackage RPC
    - the wallet, on commit and on its periodic resend (e.g. after the
      transaction came back from the network and was picked up by the
      wallet)
    - the GUI PSBT broadcast
    - ReattemptInitialBroadcast(), for transactions in the unbroadcast set
    
    All of these end up in PeerManager::InitiateTxBroadcastToAll(), which
    is also used to relay transactions received from peers. Add a reason
    parameter to it (RELAY or REQUESTED). If the reason is not RELAY and
    the transaction is known to the private broadcast storage, then do not
    announce it, remove it from the mempool's unbroadcast set, so that it
    is not retried, and log that.
    
    Relaying a privately broadcast transaction after it was received back
    from the network is not affected: at that point the node behaves like
    any other node that received the transaction.
    
    Extend p2p_private_broadcast_then_public.py to check that re-sending
    a privately broadcast transaction via submitpackage or via the periodic
    resend of the wallet does not reach a public peer, while another
    transaction sent the same way does.
    350a7f3783
  15. optout21 force-pushed on Oct 28, 2026
  16. optout21 commented at 9:40 AM on October 28, 2026: contributor

    Thanks for chiming in! Indeed, the global noban_tx_relay seems to be a cleaner way. I used LLM assistance for finding the failure reason, I've reviewed that it looks plausible, and reproduced locally (both with and without). This test doesn't care about trickle delay, so disabling it globally is a simpler solution. Changed now.

    git range-diff 0cd792580f01796d0ed16d9cf415f7b8a6ac5f20...350a7f37832cba1fc4e453806efd2f4726553968 git range-diff 7b2aae41d79624abc87198932d699ae77ec36323...350a7f37832cba1fc4e453806efd2f4726553968

  17. DrahtBot added the label Needs rebase on Oct 29, 2026
  18. DrahtBot commented at 4:45 PM on October 29, 2026: contributor

    <!--cf906140f33d8803c4a75a2196329ecb-->

    🐙 This pull request conflicts with the target branch and needs rebase.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-30 09:40 UTC