Skip to content

net_processing: make m_tx_for_private_broadcast optional#34271

Open
vasild wants to merge 1 commit intobitcoin:masterfrom
vasild:optional_m_tx_for_private_broadcast
Open

net_processing: make m_tx_for_private_broadcast optional#34271
vasild wants to merge 1 commit intobitcoin:masterfrom
vasild:optional_m_tx_for_private_broadcast

Conversation

@vasild
Copy link
Contributor

@vasild vasild commented Jan 13, 2026

Make PeerManagerImpl::m_tx_for_private_broadcast optional because it is needed for in private broadcast mode (-privatebroadcast=1).

A followup to #29415, requested in:
#29415 (comment)
https://github.com/bitcoin/bitcoin/pull/29415/files#r2620864832

@DrahtBot
Copy link
Contributor

DrahtBot commented Jan 13, 2026

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/34271.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK optout21, polespinasa, w0xlt

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:

  • #34707 (net: keep finished private broadcast txs in memory, rebroadcast if evicted from mempool by andrewtoth)
  • #34628 (p2p: Replace per-peer transaction rate-limiting with global rate limits by ajtowns)
  • #34389 (net/log: standardize peer+addr log formatting via LogPeer 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.


if (pfrom.IsPrivateBroadcastConn()) {
const auto pushed_tx_opt{m_tx_for_private_broadcast.GetTxForNode(pfrom.GetId())};
if (pfrom.IsPrivateBroadcastConn() && m_tx_for_private_broadcast.has_value()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change creates a situation that should be impossible: to have a private broadcast connection existent and have the optional m_tx_for_private_broadcast empty. Anyway it has to be handled somehow in the code and I think that maybe an assert() or Assume() is too strong? So here it would behave as if it is not a private broadcast connection. Maybe this is not the correct behavior?

I am ~0 on making m_tx_for_private_broadcast optional. In other words, it has some pros and some cons, I am fine either way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That can happen if InitiateTxBroadcastPrivate() was not called before. That could happen even without this change, and the behavior is very similar: m_tx_for_private_broadcast was initialized with an 'empty' PrivateBroadcast, so the PickTxForSend(), GetStale(), etc. methods would return nothing. However, the difference is that in that case LogDebug's are emitted. An assert might be too strong, as there is no guarantee that InitiateTxBroadcastPrivate() is called upfront, but the LogDebug messages should be emitted the same way if m_tx_for_private_broadcast none, or it is set but does not contain the expected transaction.
Another way would be to lazily init m_tx_for_private_broadcast before every access, but that may be an overkill.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That can happen if InitiateTxBroadcastPrivate() was not called before.

Ok, but how could pfrom.IsPrivateBroadcastConn() be true if InitiateTxBroadcastPrivate() was not called before?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't answer that, maybe it can't, but there is no straightforward guarantee in the code; one is a check in CNode, one is a call in PeerManagerImpl. Regardless whether it is possible (now) or not, I'm saying that the behavior regarding logging should be the same after this change than before (even if it is there is no way to get that combination today, there may be some (incorrect) change in the future, so keeping the log is a good idea).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I changed it to disconnect the peer if that ever happens in the future.


if (pfrom.IsPrivateBroadcastConn()) {
const auto pushed_tx_opt{m_tx_for_private_broadcast.GetTxForNode(pfrom.GetId())};
if (pfrom.IsPrivateBroadcastConn() && m_tx_for_private_broadcast.has_value()) {
Copy link
Contributor

@optout21 optout21 Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this approach to have the same LogDebug for the case when m_tx_for_private_broadcast is unset and there is not TX?

        if (pfrom.IsPrivateBroadcastConn()) {
            const auto pushed_tx_opt{!m_tx_for_private_broadcast.has_value() ? std::nullopt : m_tx_for_private_broadcast->GetTxForNode(pfrom.GetId())};
            if (!pushed_tx_opt) {
                LogInfo( ...
                ...
            }
            ...
        }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some logs and disconnect the peer that has IsPrivateBroadcastConn() true.

@optout21
Copy link
Contributor

ConceptACK
Review code; verified that all occurence of m_tx_for_private_broadcast is touched, verified building and unit tests locally; left some comments.

@vasild vasild force-pushed the optional_m_tx_for_private_broadcast branch from 340d10d to c218ad8 Compare January 13, 2026 15:51
@vasild
Copy link
Contributor Author

vasild commented Jan 13, 2026

340d10d26d0163201045594b30d96edd544a2f93...c218ad88764601814204d65ce21c06c851f04014: disconnect the peer if private broadcast storage is not initialized and log those events that cannot happen with the current code and must not happen in the future either.

Copy link
Member

@polespinasa polespinasa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

I don't understand why we check if m_tx_for_private_broadcast has value inside functions that imply that we are in private broadcast mode.

stale_tx->GetHash().ToString(), stale_tx->GetWitnessHash().ToString(),
mempool_acceptable.m_state.ToString());
m_tx_for_private_broadcast.Remove(stale_tx);
if (m_tx_for_private_broadcast.has_value()) {
Copy link
Member

@polespinasa polespinasa Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this check here necessary? ReattemptPrivateBroadcast is only called in StartScheduledTasks where it is only called if private broadcast option is enabled.

I mean if I understood correctly we should never get into this function if private broadcast mode is not set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same answer to the question in the main-thread: #34271 (review) and the question from #34271 (comment)

If we don't check whether the optional has value and try to access it and it does not have a value then the program will crash (it is undefined behavior). While this shouldn't be possible in the current code nothing guarantees that future changes will keep it that way. It is not immediately obvious by reading the surrounding code. So, to be more robust, it is better to have some handling of it.

This is the downside of this change any why I am ~0 about it - it adds visual clutter and unreachable code.

if (node.IsPrivateBroadcastConn() &&
!m_tx_for_private_broadcast.DidNodeConfirmReception(nodeid) &&
m_tx_for_private_broadcast.HavePendingTransactions()) {
m_tx_for_private_broadcast.has_value() &&
Copy link
Member

@polespinasa polespinasa Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a case where we can have node.IsPrivateBroadcastConn() = true and m_tx_for_private_broadcast.has_value() = false? If that's the case maybe we want to close that connection as you did in PushPrivateBroadcastTx or ProcessMessage? (c218ad8#diff-6875de769e90cec84d2e8a9c1b962cdbcda44d870d42e4215827e599e11e90e3R3542)

Edit: I see that this was already discussed here #34271 (comment) maybe want to do the same?
(I'm not really familiar with the net processing code maybe I am missing something :) )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a case where we can have node.IsPrivateBroadcastConn() = true and m_tx_for_private_broadcast.has_value() = false

Currently "no", but it is not immediately obvious and not guaranteed to remain like this after future changes.

This code here is during the connection tear down. It is being disconnected anyway already.

Copy link
Contributor

@w0xlt w0xlt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appraoch ACK

@vasild
Copy link
Contributor Author

vasild commented Feb 5, 2026

c218ad88764601814204d65ce21c06c851f04014...e1600e02a8c46b97efd47e5e6d38cf068483216f: rebase due to conflicts

Make `PeerManagerImpl::m_tx_for_private_broadcast` optional because it
is needed for in private broadcast mode (`-privatebroadcast=1`).

Requested in:
bitcoin#29415 (comment)
https://github.com/bitcoin/bitcoin/pull/29415/files#r2620864832
@vasild vasild force-pushed the optional_m_tx_for_private_broadcast branch from e1600e0 to 41f0bc0 Compare February 25, 2026 05:01
@vasild
Copy link
Contributor Author

vasild commented Feb 25, 2026

e1600e02a8c46b97efd47e5e6d38cf068483216f...41f0bc0332ff6942809812bbc76cc5fc38e9389e: rebase due to conflicts

@DrahtBot
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants