w0xlt
commented at 8:06 am on January 30, 2026:
contributor
Summary
Extends the private broadcast feature (#29415) to wallet transactions. When -privatebroadcast=1 is enabled, wallet RPCs (sendtoaddress, send, sendall, sendmany) now broadcast transactions through short-lived Tor/I2P connections instead of announcing to all connected peers.
Key Changes
Centralized availability check (commit 1)
Moves the Tor/I2P reachability check from sendrawtransaction RPC to BroadcastTransaction()
All broadcast callers now get consistent error handling
Wallet integration (commit 2)
CommitTransaction(): Uses private broadcast when enabled, fails loudly if Tor/I2P unavailable
ResubmitWalletTransactions(): Rebroadcasts using the original broadcast method
Persists a private_broadcast flag in the wallet transaction’s mapValue
Functional tests (commits 3-4)
Tests wallet send via SOCKS5 proxy
Tests flag persistence across restarts
Tests error handling when Tor/I2P unavailable
Behavior Matrix
Scenario
TX Flag
Node Setting
Expected Behavior
Tested
New tx
Private
-
Via SOCKS5, flag set
Test 1 ✓
Resubmit
Private
Public
Skip
Test 2 ✓
Resubmit
Public
Private
Skip
Test 3 ✓
Resubmit
Private
Private
Rebroadcast
Test 3 ✓
New tx
Private
- (no Tor)
RPC error
Test 4 ✓
Resubmit
Private
Private (no Tor)
Log error
Test 4 ✓
Why skip on mode mismatch? Rebroadcasting via a different method than originally used may allow correlation of transaction to origin, or may indicate origin has -privatebroadcast enabled.
DrahtBot added the label
Wallet
on Jan 30, 2026
DrahtBot
commented at 8:07 am on January 30, 2026:
contributor
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Reviews
See the guideline for information on the review process.
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:
#34533 (wallet: resubmit transactions with private broadcast if enabled by vasild)
#34410 (test: let connections happen in any order in p2p_private_broadcast.py by vasild)
#33034 (wallet: Store transactions in a separate sqlite table by achow101)
#32763 (wallet: Replace CWalletTx::mapValue and vOrderForm with explicit class members by achow101)
#29278 (Wallet: Add maxfeerate wallet startup option by ismaelsadeeq)
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 named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):
assert_raises_rpc_error(-1, “none of the Tor or I2P networks is reachable”, self.sender_wallet.sendtoaddress, dest, 0.1) in test/functional/wallet_private_broadcast.py
2026-02-07 18:54:56
DrahtBot added the label
Needs rebase
on Jan 30, 2026
andrewtoth
commented at 9:29 pm on January 30, 2026:
contributor
Concept ACK
I don’t think we need to skip rebroadcast if we initially created the transaction with private broadcast disabled. This is only important the other way - if we initially broadcast via private broadcast, but then restart with private broadcast disabled.
may indicate origin has -privatebroadcast enabled.
Ideally every node has this enabled in the future. But also, one cannot conclude this if they receive a tx both from a persistently connected node and then later via a private broadcast. This would actually make the original broadcast more private, since this would indicate that perhaps the originator is not actually the originator.
Sending decoys will also help here.
w0xlt force-pushed
on Jan 31, 2026
w0xlt
commented at 3:26 am on January 31, 2026:
contributor
@andrewtoth Thanks for the insight about plausible deniability.
Updated to only skip private→public (the other direction is now allowed):
Scenario
TX Flag
Node Setting
Behavior
Resubmit
Private
Public
Skip
Resubmit
Public
Private
Rebroadcast privately
Resubmit
Private
Private
Rebroadcast privately
Private→Public: Skip - Protects origin from being correlated
DrahtBot removed the label
Needs rebase
on Jan 31, 2026
mzumsande
commented at 4:01 pm on February 2, 2026:
contributor
Some conceptual thoughts:
The rebroadcasting behavior will probably lead to frequent 3 minute timeouts. If the tx didn’t get mined within ~24 hours due to insufficient fees, but remains in our and our peer’s mempools during this time, the peers wont’s send us a GETDATA to our repeated submission. However, I think that this would not be a huge problem because after 3 attempts, we’d abort during the retry because we check there if the tx is already in the mempool.
while this improves the rebroadcast behavior, there is still the issue that transactions relevant for the wallet but submitted via RPC (sendrawtransaction) will be received back from the network, added to mapWallet, and then rebroadcast over clearnet if the user restarted without -privatebroadcast. In master, these txns would be rebroadcast normally over clearnet even without a restart.
should something be done about feebumping behavior? I think a user sending via private broadcast, restarting without the flag and then bumping a tx would lead to the tx being sent via clearnet.
w0xlt force-pushed
on Feb 7, 2026
w0xlt force-pushed
on Feb 7, 2026
DrahtBot added the label
CI failed
on Feb 7, 2026
DrahtBot
commented at 10:00 am on February 7, 2026:
contributor
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.
w0xlt
commented at 10:13 am on February 7, 2026:
contributor
I think that this would not be a huge problem because after 3 attempts, we’d abort during the retry
Agreed.
there is still the issue that transactions relevant for the wallet but submitted via RPC (sendrawtransaction) will be received back from the network
As far as I can see there is no simple solution to this scenario. Even if we have a persistent database of privately broadcast transactions (like proposed in #34322) and the wallet could query it when a relevant transaction is received, the user can submit the transaction from a different node than the wallet’s node. One approach would be for the wallet to mark all IsFromMe() transactions not originated through CommitTransaction() as private. Not sure if that is an acceptable solution but it would cover this case unless I am missing something.
should something be done about feebumping behavior?
Added a solution and a functional test for this case. Thanks for raising it.
w0xlt
commented at 10:15 am on February 7, 2026:
contributor
CI error is unrelated
w0xlt force-pushed
on Feb 7, 2026
w0xlt force-pushed
on Feb 7, 2026
w0xlt force-pushed
on Feb 7, 2026
node: move private broadcast availability check to BroadcastTransaction()
Move the Tor/I2P network reachability check from the sendrawtransaction RPC
to BroadcastTransaction(). This centralizes the check so that all callers
(including wallet RPCs) get consistent behavior when using private broadcast.
Changes:
- Add TransactionError::PRIVATE_BROADCAST_UNAVAILABLE enum value
- Add corresponding error message in common/messages.cpp
- Add RPC error code mapping in rpc/util.cpp
- Move the check from rpc/mempool.cpp to node/transaction.cpp
- Set err_string when the error occurs for proper error propagation
7ad771b363
wallet: integrate private broadcast for wallet transactions
Enable private broadcast for wallet transaction submission when
-privatebroadcast is set. This includes both initial sends and
rebroadcasts, with persistent tracking to prevent privacy leaks.
Changes to CommitTransaction():
- Use NO_MEMPOOL_PRIVATE_BROADCAST method when -privatebroadcast enabled
- Store "private_broadcast" flag in wallet transaction mapValue
- Throw on broadcast failures (e.g., Tor/I2P not reachable) instead of
silently continuing
Changes to ResubmitWalletTransactions():
- Use private broadcast for resubmission when -privatebroadcast enabled
- Skip privately-sent txs when node has public setting (prevents IP leak)
- Allow publicly-sent txs to rebroadcast privately (provides plausible deniability)
- Log errors when rebroadcast fails due to Tor/I2P unavailability
Changes to feebumper::CommitTransaction():
- Refuse to fee-bump a privately-broadcast tx when -privatebroadcast is
off. The replacement spends the same inputs, so broadcasting it over
clearnet would link this node's IP to the original private transaction.
The persistence logic is essential for privacy: without it, a node
restart with -privatebroadcast disabled could cause private transactions
to be rebroadcast publicly, leaking information about the transaction
origin.
b58d0c337b
test: add Socks5ProxyHelper and add_addresses_to_addrman helpers
Add helper utilities to test_framework/socks5.py to reduce code duplication
in private broadcast tests:
- Socks5ProxyHelper: Simplifies SOCKS5 proxy setup with ephemeral ports,
thread-safe destination tracking, and simple redirect factory
- add_addresses_to_addrman(): Helper to populate a node's addrman with
test addresses
- FAKE_ONION_ADDRESSES: Shared list of fake .onion addresses for tests
Update p2p_private_broadcast.py to use ephemeral ports and the new
add_addresses_to_addrman helper.
57cbd23286
test: add wallet_private_broadcast.py functional test
Add a functional test for wallet-specific private broadcast behavior:
- Test 1: sendtoaddress with -privatebroadcast sends via SOCKS5 proxy
- Test 2: private_broadcast flag persists and prevents public rebroadcast
- Test 3: Public txs rebroadcast privately (provides plausible deniability)
- Test 4: Error when Tor/I2P not reachable
- Test 5: Fee bump refused when -privatebroadcast is off (prevents clearnet leak)
- Test 6: Fee bump succeeds and broadcasts privately when -privatebroadcast is on
Uses Socks5ProxyHelper for simplified proxy setup and connection tracking.
3645cccf8a
w0xlt force-pushed
on Feb 7, 2026
DrahtBot removed the label
CI failed
on Feb 7, 2026
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-02-09 18:13 UTC
This site is hosted by @0xB10C More mirrored repositories can be found on mirror.b10c.me