[RFC] Scheduled transaction broadcast, minimal #36429

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

    Minimal implementation for "Scheduled Transaction Broadcast" new feature.

    Goal: Allow a user to submit a transaction to be broadcast later, at a specified time (block height). This can be done for convenience, or to hide the exact time of the transaction creation (for increased privacy).

    Goal in this PR: Extend the sendrawtransaction RPC with an option to schedule the broadcast at a later time, determined by the lock time of the transaction.

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

    • sendrawtransaction has an optional parameter, delay_after_lock_time
    • If delay_after_lock_time is set to 0, the nLockTime of the transaction determines the scheduled broadcast time (in the future).
    • If delay_after_lock_time is set to a positive number, the broadcast time will be the sum of the transaction nLockTime and the parameter value.
    • Only height-based lock times are supported, time-based can be added later.
    • Scheduled broadcast is only supported with private broadcast.
    • What is a typical delay time? 3-12 blocks. (Too long times can get problematic: risk fee increase, node restart.)
    • Pending transactions are NOT persisted, so pending transactions will be lost during a node stop/restart/crash (can be added later).
    • Validation should happen at both submission time and broadcast time, but now this is not true, see below.
    • If the goal is to conceal the real transaction creation time, the user should add a relatively short, random period to the current time (e.g between 3-12 blocks).
    • 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 support from the wallet subsystem. (A submitted but not broadcast transactions is not visible in the wallet.)
    • It is possible intentionally set the locktime of a TX in the past (e.g. as a measure against wallet fingerprinting), and still use a later schedule time -- delay_after_lock_time with a positive value can be used to distance the submission time from the lock time.

    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). Alternative: triggered by new blocks.
    • Queue is not persisted currently, to be added later.
    • Submit-time validation is skipped, as it would fail, due to the future locktime. To be added later.

    Further improvements:

    • Add a cap on the time lock, on how far in the future it is acceptable, e.g. max 7 days / 1008 blocks.
    • Support time-based locktime as well. Interpret delay_after_lock_time in 10-minute units if time-based lock is used.
    • At submission time, perform validation but without timelock checks. This requires a small validation change (add bypass timelocks options to methods ProcessTransaction and AcceptToMemoryPool).
    • Persist the queue with pending broadcasts, so scheduled broadcasts survive node restarts.

    Related issue: #34118

    EDIT (Nov-03): Reworked, see #36429 (comment)

  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:

    • #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)
    • #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-->

  3. optout21 force-pushed on Oct 5, 2026
  4. optout21 force-pushed on Oct 6, 2026
  5. 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.

  6. DrahtBot added the label CI failed on Oct 6, 2026
  7. 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".

  8. 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.

  9. 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.

  10. optout21 force-pushed on Oct 6, 2026
  11. DrahtBot removed the label CI failed on Oct 6, 2026
  12. 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?
  13. 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)

  14. optout21 renamed this:
    Delayed transaction broadcast, minimal
    [RFC] Delayed transaction broadcast, minimal
    on Oct 9, 2026
  15. vasild commented at 4:59 AM on October 21, 2026: contributor

    Even better, nLockTime could be used to set/extend the requested broadcast time.

    Yes, I like this idea - use nLockTime for deciding when to send the transaction. Because:

    • If a transaction is sent before the nLockTime height is reached, then it cannot confirm, so it does not make sense to send it earlier.
    • If a transaction is sent after the nLockTime height is reached, then it trivially reveals when the transaction was created (nLockTime) and a delayed send will not hide it. So it does not make sense from privacy perspective.

    (The time-based option should be there nonetheless, as it makes the feature more accessible.)

    If the point is to hide the transaction creation time and the wallet puts current height for nLockTime (like the Bitcoin Core wallet mostly does) then sending later does not make sense. So such an option could mislead users that they can hide the transaction creation time by using it?

  16. optout21 commented at 12:02 PM on October 21, 2026: contributor

    Following some recent discussions, I plan to do some changes to the feature:

    • Acknowledge the two separate sub use cases of scheduling (for convenience) and random delay (for time obfuscation).

    • Use the 'nLockTime' of the transaction as a basis for scheduling time, instead of an extra parameter. This way it can be ensured that the scheduling time and lock time are in sync (or if they are not, that's intended).

    • Drop the random delay computation, because (1) it's not compatible with the lock time usage of the previous point, and (2) it is easy to be done on the client side

    Keeping in Draft status until the above changes are applied.

  17. optout21 commented at 12:09 PM on October 21, 2026: contributor

    Yes, I like this idea - use nLockTime for deciding when to send the transaction @vasild , thanks for this and previous feedback! I also arrived at the same conclusion, see the previous comment.

    I am thinking to also support the option for using a past lock time for obfuscation (wallet fingerprinting). One idea I came up with:

    • In sendrawtransaction RPC introduce a new parameter delay_after_lock_time
    • If not set, there is no delay
    • If set (usually to 0), the delay is the sum of nLockTime and the parameter value

    Examples (assume current height is 1000:

    • The wish is to broadcast only at height 1005: set lock time to 1005, and delay_after_lock_time=0
    • The wish is to broadcast only at height 1005, but lock time set to 985: set lock time to 985, and delay_after_lock_time=20. Broadcast will be delayed until height 1005 (985+20).

    Scheme to be refined. It should also support time-based lock time.

  18. net: Support scheduled release in private broadcast queue and logic
    Core support for scheduled private broadcast:
    - Add an optional release height to submitted transactions;
      `release_height` field added to `TxBroadcastInfo` and `TxSendStatus`
    - Add ReleaseDue() to release the scheduled transactions whose release
      height has been reached by the given block height
    - Adapt logic to handle scheduled 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()
    - Measure staleness from the time of release
    35ab3042e7
  19. net: Support scheduled broadcasts in PeerManager
    Only send scheduled transactions after the active chain reaches their
    release height. A periodic task, similar to ReattemptPrivateBroadcast(),
    releases the transactions that became due, re-validates them, and
    requests connections for the ones that are still valid.
    9ed66beade
  20. rpc: Add delay_after_lock_time parameter to sendrawtransaction RPC
    If set, and private broadcast is enabled (prerequisite), the broadcast of
    the submitted transaction is scheduled for the block height
    nLockTime + delay_after_lock_time. Only height-based lock times are
    supported. If the release height has already been reached, the
    transaction is broadcast immediately.
    
    A scheduled transaction is validated at submission. If it is rejected
    only as TX_PREMATURE_SPEND (e.g. its nLockTime is in the future), it is
    still accepted, as it may become valid by its release height; the checks
    that ATMP did not get to are then only done at release, when the
    transaction is fully validated again.
    getprivatebroadcastinfo shows the release height of scheduled
    transactions.
    de01047c35
  21. test: Add scheduled broadcast functional test 225c2c72f4
  22. doc: Add release note for sendrawtransaction delay_after_lock_time 8960630481
  23. optout21 force-pushed on Nov 3, 2026
  24. optout21 renamed this:
    [RFC] Delayed transaction broadcast, minimal
    [RFC] Scheduled transaction broadcast, minimal
    on Nov 3, 2026
  25. optout21 commented at 10:15 AM on November 3, 2026: contributor

    Reworked the whole PR (code, description, title) in light of the above, to timelock-based.


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-11-03 23:51 UTC

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