rpc,net: Add private broadcast RPCs #34329

pull andrewtoth wants to merge 6 commits into bitcoin:master from andrewtoth:private_broadcast_rpcs changing 9 files +288 −1
  1. andrewtoth commented at 1:46 am on January 18, 2026: contributor

    Follow up from #29415

    Sending a transaction via private broadcast does not have any way for a user to track the status of the transaction before it gets returned by another peer. The default logs will likely be removed as well in #34267. Nor is there any way to abort a transaction once it has been added to the private broadcast queue.

    This adds two new RPCs:

    • getprivatebroadastinfo returns information about what transactions are in the private broadcast queue, including how many times and how recently the transactions have been sent and acknowledged by peers.
    • abortprivatebroadcast removes a transaction from the private broadcast queue.
  2. net: Add PrivateBroadcast::GetBroadcastInfo fb2a71cf6d
  3. DrahtBot commented at 1:46 am on January 18, 2026: contributor

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

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34329.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK vasild

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34322 (node: Persist private broadcast transactions over node restarts by andrewtoth)
    • #34049 (rpc: Disallow captures in RPCMethodImpl by ajtowns)

    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.

    LLM Linter (✨ experimental)

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

    • test/functional/p2p_private_broadcast.py: assert pending[0][“sent_count”] >= NUM_PRIVATE_BROADCAST_PER_TX -> use assert_greater_than_or_equal(pending[0][“sent_count”], NUM_PRIVATE_BROADCAST_PER_TX)
    • test/functional/p2p_private_broadcast.py: assert pending[0][“peer_reception_ack_count”] >= NUM_PRIVATE_BROADCAST_PER_TX -> use assert_greater_than_or_equal(pending[0][“peer_reception_ack_count”], NUM_PRIVATE_BROADCAST_PER_TX)

    No other comparison macro suggestions were found.

    2026-01-18

  4. DrahtBot added the label CI failed on Jan 18, 2026
  5. DrahtBot commented at 3:04 am on January 18, 2026: contributor

    🚧 At least one of the CI tasks failed. Task Windows native, fuzz, VS 2022: https://github.com/bitcoin/bitcoin/actions/runs/21104117103/job/60692665532 LLM reason (✨ experimental): Fuzzing failed because the RPC command “abortprivatebroadcast” is not registered as safe or not-safe for fuzzing in rpc.cpp.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  6. rpc: Add getprivatebroadcastinfo 04fc7fe02f
  7. andrewtoth force-pushed on Jan 18, 2026
  8. andrewtoth force-pushed on Jan 18, 2026
  9. test: Cover getprivatebroadcastinfo in p2p_private_broadcast 661eb43292
  10. rpc: Add abortprivatebroadcast 57cc74dcbc
  11. test: Cover abortprivatebroadcast in p2p_private_broadcast 53ba3a708b
  12. doc: Mention private broadcast RPCs in release notes ac6c2bd064
  13. andrewtoth force-pushed on Jan 18, 2026
  14. in src/test/private_broadcast_tests.cpp:133 in ac6c2bd064
    128+        BOOST_REQUIRE(it_confirmed != infos.end());
    129+        BOOST_REQUIRE(it_unconfirmed != infos.end());
    130+        const auto& confirmed_tx{*it_confirmed};
    131+        const auto& unconfirmed_tx{*it_unconfirmed};
    132+
    133+        BOOST_CHECK_EQUAL(confirmed_tx.num_peer_reception_acks, 1);
    


    vasild commented at 9:15 am on January 21, 2026:

    I picked “confirmed” for PrivateBroadcast::SendStatus::confirmed, but looking at it now…

    “Confirmed” overlaps with an already established term, meaning “included in a block”. So, maybe better to use any other word, but not “confirmed”.

    “received”? “reception confirmed”? “broadcast”?

    Or at least keep this in mind ;-) Feel free to ignore.

  15. in src/private_broadcast.cpp:128 in ac6c2bd064
    123+    }
    124+
    125+    // Ensure stable output order (m_transactions is an unordered_map).
    126+    std::sort(entries.begin(), entries.end(), [](const TxBroadcastInfo& a, const TxBroadcastInfo& b) {
    127+        return std::tie(a.tx->GetHash(), a.tx->GetWitnessHash()) < std::tie(b.tx->GetHash(), b.tx->GetWitnessHash());
    128+    });
    


    vasild commented at 9:22 am on January 21, 2026:
    Sorting seems unnecessary. It might be difficult to drop it later if users get used to it and start depending on it.
  16. in src/private_broadcast.h:39 in ac6c2bd064
    34+        CTransactionRef tx;
    35+        size_t num_sent{0};
    36+        std::optional<NodeClock::time_point> last_sent{};
    37+        size_t num_peer_reception_acks{0};
    38+        std::optional<NodeClock::time_point> last_peer_reception_ack{};
    39+    };
    


    vasild commented at 9:41 am on January 21, 2026:

    It would be useful to show also the addresses the transaction was sent to and some additional information. Like so:

     0[
     1    {
     2        "txid": "tx1",
     3        "sent": [
     4            {
     5                "address": "aaa.onion:8333",
     6                "begin": "2026.01...", // time when picked for sending
     7                "end": "2026.01..." // time when reception was confirmed by the peer
     8            },
     9            {
    10                "address": "bbb.onion:8333",
    11                "begin": "2026.01...",
    12                "end": "2026.01..."
    13            }
    14        ],
    15        "received": {
    16            "address": "1.2.3.4:8333", // received back from the network from this peer
    17            "when": "2026.01..." // at this time
    18        }
    19    }   
    20]
    

    This would mean to not immediately drop the statistics when we receive the transaction back from the network, but keep the stats for some time, in order to be able to show them to the user. Maybe keep this info for e.g. 24 hours or better, keep the last N entries.

  17. in test/functional/p2p_private_broadcast.py:425 in ac6c2bd064
    420@@ -395,8 +421,8 @@ def run_test(self):
    421         with tx_originator.busy_wait_for_debug_log(expected_msgs=[rebroadcast_msg.encode()]):
    422             tx_originator.setmocktime(int(time.time()) + delta)
    423             tx_originator.mockscheduler(delta)
    424-        self.check_broadcasts("Rebroadcast", txs[1], 1, skip_destinations)
    425         tx_originator.setmocktime(0) # Let the clock tick again (it will go backwards due to this).
    426+        self.check_broadcasts("Rebroadcast", txs[1], 1, skip_destinations)
    


    vasild commented at 9:55 am on January 21, 2026:
    Why reorder this?
  18. in src/net_processing.h:127 in ac6c2bd064
    119@@ -118,6 +120,12 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
    120     /** Get peer manager info. */
    121     virtual PeerManagerInfo GetInfo() const = 0;
    122 
    123+    /** Get info about transactions currently being privately broadcast. */
    124+    virtual std::vector<PrivateBroadcast::TxBroadcastInfo> GetPrivateBroadcastInfo() const = 0;
    125+
    126+    /** Abort private broadcast attempts for a transaction (identified by txid or wtxid). */
    127+    virtual std::vector<CTransactionRef> AbortPrivateBroadcast(const uint256& id) = 0;
    


    vasild commented at 10:01 am on January 21, 2026:
    Would be good to document the return value. It may not be immediately obvious why it is a vector.
  19. in src/rpc/mempool.cpp:234 in ac6c2bd064
    229+            const NodeContext& node{EnsureAnyNodeContext(request.context)};
    230+            PeerManager& peerman{EnsurePeerman(node)};
    231+
    232+            const auto removed_txs{peerman.AbortPrivateBroadcast(id)};
    233+            if (removed_txs.empty()) {
    234+                throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in private broadcast queue");
    


    vasild commented at 10:03 am on January 21, 2026:
    Could point the user here to go look at the getprivatebroadcastinfo RPC to see what is in the queue.
  20. vasild approved
  21. vasild commented at 10:13 am on January 21, 2026: contributor

    ACK ac6c2bd06485ff6e7ed538a234f20491d3cf47cb

    Nice and straight forward, thanks!

    I think we can do better by showing more info, but this does not make the current PR worse.


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-01-22 15:13 UTC

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