[RFC] Delayed transaction broadcast, minimal #36429

pull optout21 wants to merge 6 commits into bitcoin:master from optout21:2610-delayed-broadcast1 changing 12 files +488 −28
  1. optout21 commented at 6:01 PM on October 3, 2026: contributor

    Minimal implementation for "Delayed Transaction Broadcast" new feature.

    Goal: Allow a user to submit a transaction for delayed broadcasting, that will be broadcast only later, at a randomized time. The aim is increased privacy, by making time-correlation-based tracking more difficult. This fits together with private broadcast: private connection broadcast tries to hide the originator node; randomized delay tries to hide the creation time.

    Goals in this PR: Extend the sendrawtransaction RPC with an optional delay_for parameter, and if set, postpone the initial broadcast to that later time, with some extra jitter.

    Use case Details, with the limitations of the current changeset:

    • What is a typical delay time? 30 - 90 minutes. (Too long times can get problematic: risk fee increase, node restart.)
    • Delayed broadcast is only supported with private broadcast.
    • Delay option and time is specified per submission (in sendrawtransaction). Alternative: with node level config.
    • For now pending transactions are NOT persisted, so pending transactions will be lost during a node stop/restart/crash.
    • Randomized delay is expressed as a percentage of the requested delay. A hard-coded value of 50% is used.
    • Delay times are capped: requested delay between 0min to 12 hours, randomization range to minimum 5 minutes.
    • Validation happens both at submission and broadcast time.
    • No special handling for sudden fee increase situations.
    • Pending (submitted but not yet broadcast) transactions can be inspected via existing getprivatebroadcastinfo RPC.
    • The (pre-existing) abortprivatebroadcast RPC can be used to cancel a pending transaction submission.
    • No special nLockTime handling, to be refined later.
    • No special support from the wallet subsystem. (A submitted but not broadcast transactions is not visible in the wallet.)

    Solution Details:

    • The solution is based on the Private Broadcast implementation.
    • Transactions are stored in the already-existing m_transactions collection.
    • A scheduled periodic check is added to check for transactions that have became due. The implementation is very similar to the existing retry logic (ReattemptPrivateBroadcast).
    • Queue is not persisted currently, to be added later.
  2. DrahtBot commented at 6:02 PM on October 3, 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:

    • #36477 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36477.svg"></sub> (private broadcast: show finished transactions in getprivatebroadcastinfo by vasild)
    • #35888 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/35888.svg"></sub> (net: reject oversized outbound messages by l0rinc)

    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:

    • doc/release-notes-delayed-private-broadcast.md: “up to 50% of it (and up to at least 5 minutes) extra” → “up to 50% of the requested delay, or 5 minutes, whichever is greater” [“up to at least” is contradictory and obscures the jitter range]

    Possible places where comparison-specific test macros should replace generic comparisons:

    • test/functional/p2p_private_broadcast.py: assert len(entries) <= 1 → Prefer assert_greater_than_or_equal(1, len(entries)).

    <sup>2026-10-06 21:45:02</sup>

  3. optout21 force-pushed on Oct 5, 2026
  4. net: Support delay in private broadcast queue and logic
    Core support for delayed private broadcast:
    - Add target release time to submitted transactions;
      `release_time` field added to `TxBroadcastInfo` and `TxSendStatus`
    - Adapt logic to handle delayed submission (Add, Remove, etc.):
      a transaction is not sent before it is released, has no planned
      sends to cancel on Remove(), and is forgotten by MarkResolved()
    3b837c116c
  5. net: Support delayed broadcasts in PeerManager
    Only send delayed transactions after they are due.
    c29b738405
  6. net: Add randomization of private broadcast delay
    Add constants and the RandomizeDelay() helper to randomize the release
    time of a delayed private broadcast, and use it in PeerManager.
    The randomized delay is never shorter than requested, and the requested
    delay is capped at MAX_DELAY.
    dd4e115ccf
  7. rpc: Add delay_for parameter to sendrawtransaction RPC
    If set, and private broadcast is enabled (prerequisite), the submitted
    transaction will be delayed.
    92ccc14c31
  8. optout21 force-pushed on Oct 6, 2026
  9. optout21 commented at 11:23 AM on October 6, 2026: contributor

    Rebased. #36277 got merged, which caused the first commit to be substantially changed, but behavior is the same.

  10. DrahtBot added the label CI failed on Oct 6, 2026
  11. vasild commented at 1:27 PM on October 6, 2026: contributor

    Putting the current block height in nLockTime when the transaction is created defeats delaying its broadcast:

    https://github.com/bitcoin/bitcoin/blob/acaf322fc438e6bd180b23189a5661c8f02d88fb/src/wallet/spend.cpp#L1020-L1030

    So an artificial delay must be combined with a non-default nLockTime in order to be effective. External wallets that use the RPC and use other nLockTime might benefit more from this.


    Delaying in order to make linking transactions (that are otherwise unrelated) by creation time harder makes sense if private broadcast is used. If send-to-all broadcast is used, then the otherwise unrelated transactions can be linked as coming from the same node.


    The delay does not play nice with node restart (unbroadcast transactions would be lost).


    The (pre-existing) abortprivatebroadcast RPC can be used to cancel a pending transaction submission No Undo/Cancel support at this time (to be added later).

    Hmm?


    Nit, from the OP:

    Pending (submitted but not yet submitted)

    probably you meant "submitted but not yet broadcast".

  12. optout21 commented at 2:21 PM on October 6, 2026: contributor

    @vasild, thanks for the general remarks!

    • nLockTime: In the current PR nLockTime is not considered ("No special nLockTime handling").

    The Core wallet could be enhanced, to use matching nLockTime if using delyed broadcast (in a followup). Also, delayed broadcast could check and warn on nLockTime (closer to submission time than broadcast time, or too much in the future). Even better, nLockTime could be used to set/extend the requested broadcast time. (The time-based option should be there nonetheless, as it makes the feature more accessible.)

    If send-to-all broadcast is used, then the otherwise unrelated transactions can be linked

    True, but delayed broadcast is available only in conjunction with private broadcast.

    • Persisting the queue is an important feature, should be added (but I thing a followup is fine).

    • Undo/Cancel support: that's a mistake in the description, removed (originally was not planned, but it came 'free' with existing private broadcast infrastructure).

    • Typo, fixed.

  13. test: Add delayed broadcast functional test 3d1d85c107
  14. doc: Add release note for sendrawtransaction delay_for f692a4860b
  15. optout21 commented at 9:42 PM on October 6, 2026: contributor

    Applied a fix to the new functional test. One CI run failed, and it turned out to be a timing issue in the test. Note that the failing run was a no wallet run, but the sporadic failure is not specific to the no-wallet config. Pre-#36277 the test was correct, but now it had to be adapted.

  16. optout21 force-pushed on Oct 6, 2026
  17. DrahtBot removed the label CI failed on Oct 6, 2026
  18. ismaelsadeeq commented at 6:23 AM on October 7, 2026: member

    Hmm, I am tending towards concept ~0, As I mentioned here #34118, the delay itself seems like something a client can already do: sleep for a random time, then call sendrawtransaction on a node running with -privatebroadcast. I had Claude write a ~30 line shell script that reproduces the algorithm here.

    But the shell script has downsides, the client/shell process has to stay running for up to 12 hours, whereas with this PR the node holds it instead, which is better. Info calls like visibility in getprivatebroadcastinfo and aborting via abortprivatebroadcast aren't available to a script either. But as the PR is, pending txs aren't persisted and are lost on restart, so the node has to stay up too (so having the client up vs. the node up is the same tradeoff).

    It would be a bit more ideal if this were implemented for our wallet users, since it'd be awkward for them to create a tx and then have to use a script to delay broadcasting it.

    Also see the recent discussion in #36403, which attempts to revive a lot of old ideas like a node rebroadcast module, improving wallet rebroadcast, e.t.c. it would involve some machinery to distinguish between delayed-rebroadcast txs, mempool txs, private-rebroadcast txs, and wallet txs, since they'd all need to be taken into account, but in practice can be done correctly with good SOC.

    Fwiw, I am not completely dismissing this, I'd like to understand the privacy goal better. "Making time-correlation-based tracking more difficult" is a bit vague can you be more explicit, e.g. with scenarios and data? Because I think I'm missing something:

    1. What is the concrete privacy gain?
    2. What's the actual harm in an attacker knowing the creation time? Given that private broadcast already hides which node sent it, what does also hiding when it was made protect against in practice?
  19. optout21 commented at 4:29 PM on October 9, 2026: contributor

    Hey @ismaelsadeeq , thanks for sharing your concerns!

    the delay itself seems like something a client can already do.

    That's true, most client could do the delay themselves (this is not true for private broadcast, for which the core node must be involved). However, many clients can not do this easily, as wallets are typically not used in an always-on mode. If someone uses a third-party wallet software (e.g. Sparrow, Nunchuk, etc.), connected to his own core node (directly or indirectly via an electrum server), ultimately the core node is the always-on component interfacing with the bitcoin network, and it's a good fit to offer scheduled/delayed broadcast support. I don't think shell script is a practical solution for an average user, even a power user.

    For more concrete examples, here are some use cases:

    • A user manages TXs through a non-core wallet, connected to a core node. He wants to initiate a few transfers for some other persons, to be done the next day, without a need for a future user interaction, or keeping the wallet running, or leaving an early trace in the mempool/blockchain. Since the code node is used to interface with the network, and it is an always-on component, it makes sense to delegate. Note that this is a simple scheduled broadcast example.

    • A user manages TXs through a non-core wallet, connected to a core node. He wants to initiate a few transfers for some other persons, initiated at once, but with different public timing fingerprint (care is taken that the TXs are not related through UTXOs). The user does not want a future user interaction, or keeping the wallet running.

    • A service backend sometimes creates bitcoin transactions (through a core node), in response to some events, e.g. a trading/betting system. The triggering events are publicly observable, but not on the bitcoin network. Transactions related to specific events could be probabilistically matched based on timing analysis. To avoid that, the service wants to create the transactions with a random delay. While not the only solution, but it's preferred that the core node can handle delayed broadcast.

    attempts to revive a lot of old ideas like a node rebroadcast module

    I think private broadcast, delayed broadcast, rebroadcast all fit in a larger picture: locally-originated transactions. Transactions originated in/through the node are special in many ways compared to relayed transactions originated elsewhere.

    Sources:

    • The bitcoin core wallet
    • External clients -- wallets, services, etc, using core node (via RPC; directly or indirectly)

    Functionalities:

    • Rebroadcast
    • Masking of origin -- private broadcast
    • Scheduled/delayed broadcast
    • RBF
    • Undo (if possible)

    I would argue that an abstraction for local-originated transactions and a pool for such transactions would make a lot of sense.

    (Note: added '[RFC]' prefix to title)

  20. optout21 renamed this:
    Delayed transaction broadcast, minimal
    [RFC] Delayed transaction broadcast, minimal
    on Oct 9, 2026

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-14 18:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me