net: keep finished private broadcast txs in memory, rebroadcast if evicted from mempool#34707
net: keep finished private broadcast txs in memory, rebroadcast if evicted from mempool#34707andrewtoth wants to merge 9 commits intobitcoin:masterfrom
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please copy-paste ConflictsReviewers, this pull request conflicts with the following ones:
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 typos and grammar issues:
2026-03-12 00:12:34 |
a88810a to
afca844
Compare
|
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
src/private_broadcast.cpp
Outdated
| const auto it{m_transactions.find(tx)}; | ||
| if (it == m_transactions.end()) return; | ||
| it->second.mempool_rejection_reason = std::move(rejection_reason); | ||
| if (!it->second.mempool_rejection_reason.has_value()) { |
There was a problem hiding this comment.
I find the dependency between rejection_reason and received_from confusing here. If the TX can be accepted in the mempool, the received from info is cleared. Could you elaborate on the why?
Update: I found it described in the header comment, but I still don't understand fully (may be my problem).
There was a problem hiding this comment.
I updated this logic and added a clarifying comment.
It only clears the received from info now if there is a transition from mempool rejected -> mempool acceptable. This means the tx has been evicted from our mempool for some reason that does not cause a conflict with it. In this case, it is like we never received the tx from a peer. It would be confusing to have a state where we received it but it is not in our mempool and not conflicted or confirmed. So, we clear received state and try the broadcasting flow again.
Now we also pick txs if they have are acceptable to our mempool and have no received from state. So, it is necessary to clear this data to ever have this tx picked for private broadcast again.
There was a problem hiding this comment.
I thought about this some more, and it seems it was as confusing as it was because I was trying to use a single piece of state to represent two different possible states. If mempool_rejection_reason was not nullopt it could mean either the tx is not in the mempool and is not acceptable, or is currently in the mempool.
I split this out into an enum with 3 states:
IN_MEMPOOL,
ACCEPTABLE,
REJECTED.
Now only if we are REJECTED will we store a reason string.
This simplifies the logic since only ACCEPTABLE txs will be picked for broadcast and returned as pending.
|
A more important conceptual point I'd like to raise: As I see:
In short, for the newly considered features I consider the "locally-originated TX" to be more relevant than the "private" nature of the broadcast, and suggest using it in namings. (fyi @vasild) |
| }); | ||
| } | ||
|
|
||
| std::vector<CTransactionRef> PrivateBroadcast::GetStale() const |
There was a problem hiding this comment.
GetStale() needs to be changed to omit the concluded (finished) transactions.
There was a problem hiding this comment.
I was thinking more about this and I don't see a way to have "finished" transactions.
If a tx is acceptable to our mempool, it is not yet finished because it has not been received.
If a tx exists in our mempool, it is not yet finished because it has not been mined and may be evicted.
If a tx is not acceptable to our mempool, it or a conflicting tx may have been mined. However, it may also be the case that its parent tx has been evicted from our mempool as well and not yet mined. Either way, checking the confirmed utxo set will also show this tx as unacceptable, when it will be acceptable if the parent enters the mempool again. This tx is not yet finished since it may be acceptable to our mempool again if the parent returns.
So, is there a heuristic we can use to determine if a tx is concluded (finished)? I don't see one. We need to keep txs in the queue indefinitely.
One way is to have an expiry on txs which you suggested in #34322, but I believe that is out of scope for this PR.
2c3c80d to
9f58d9a
Compare
|
Thank you for your reviews @optout21 and @vasild.
Also addressed several other refactor and style changes, and split up and reordered commits for easier review.
@optout21 that's an interesting point. I do think it is out of scope for this PR though. Could be raised independently. |
9f58d9a to
c9b19d7
Compare
|
Updated test to fix a bad rebase. Also, addressed feedback to make rebroadcast logic more clear #34707 (comment).
|
c9b19d7 to
6ecbf18
Compare
This is a non-functional change. Wrapping the sent_to vector in a struct lets us add more fields to each tx.
Introduce MempoolState enum (IN_MEMPOOL, ACCEPTABLE, REJECTED) and UpdateMempoolState method in PrivateBroadcast. Add GetPendingTransactions to filter by ACCEPTABLE state. Only pick pending txs for sending. Don't remove txs when not acceptable to mempool in stale check; instead track their rejection state.
6ecbf18 to
911d4eb
Compare
Introduce Received method in PrivateBroadcast to record which peer relayed the transaction back to us. The transaction stays in the queue instead of being removed, preserving broadcast metadata.
Non-functional refactor.
911d4eb to
bd31f73
Compare
Follow up from #34329.
Private broadcast transaction data is removed from memory when either 1) the tx is received back from another peer and inserted into our mempool or 2) the tx is no longer acceptable to our mempool.
We want to persist this data for a number of reasons. See #30471.
This PR changes the behavior of the private broadcast queue to keep received and mempool-rejected txs in memory. The peer's address we received the tx from and the time, or the mempool rejection reason is now also returned via
getprivatebroadcastinfoRPC.We store the txs mempool state as either
IN_MEMPOOL,ACCEPTABLE, orREJECTED. Only txs that are currently not in the mempool but are acceptable will be picked for broadcast and returned as pending transactions. This maintains the current behavior of stopping broadcasting when these conditions are met.When reattempting to broadcast stale private broadcast txs, every tx whether pending or not is checked for mempool acceptance. This has the added benefit of rebroadcasting previously received txs that have since been evicted from the mempool.
This is designed to facilitate follow up work including: