Skip to content

backport: bitcoin#19988 - Overhaul transaction request logic#5943

Open
knst wants to merge 1 commit into
dashpay:developfrom
knst:bp-v21-p26-19988
Open

backport: bitcoin#19988 - Overhaul transaction request logic#5943
knst wants to merge 1 commit into
dashpay:developfrom
knst:bp-v21-p26-19988

Conversation

@knst

@knst knst commented Mar 19, 2024

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

This replaces the transaction request logic with an encapsulated class that maintains all the state surrounding it. By keeping it stand alone, it can be easily tested (using included unit tests and fuzz tests).

What was done?

The major changes are (from original PR description):

  • Announcements from outbound (and whitelisted) peers are now always preferred over those from inbound peers. This used to be the case for the first request (by delaying the first request from inbound peers), and a bias afters. The 2s delay for requests from inbound peers still exists, but after that, if viable outbound peers remain for any given transaction, they will always be tried first.
  • No more hard cap of 100 in flight transactions per peer, as there is less need for it (memory usage is linear in the number of announcements, but independent from the number in flight, and CPU usage isn't affected by it). Furthermore, if only one peer announces a transaction, and it has over 100 in flight already, we still want to request it from them. The cap is replaced with a rule that announcements from such overloaded peers get an additional 2s delay (possibly combined with the existing 2s delays for inbound connections, and for txid peers when wtxid peers are available).

Differences between Bitcoin Core and Dash Core

Bitcoin uses the inv/getdata machinery only for transactions;
Dash uses it for many object types (governance objects and votes, InstantSend locks, ChainLocks, sporks, coinjoin queues, quorum messages).
This backport therefore generalizes TxRequestTracker for CInv.

  • Announcement identity is the full CInv (type and hash), not a txid/wtxid.
    Announcementss of the same hash under two types (MSG_TX vs MSG_DSTX) are
    tracked independently, and the priority function hashes the type too.
  • Delay policy: the 2s NONPREF_PEER_TX_DELAY applies only to MSG_TX
    announcements from non-preferred peers, and not in masternode mode --
    preserving current Dash behavior. Dash-specific object types are never
    delayed at announcement time: they carry time-sensitive consensus data
    (DKG and signing would stall otherwise).
  • Request expiry is per-type via the pre-existing GetObjectInterval()
    (CLSIG 5s, ISDLOCK 10s, recovered sig 15s, default 60s). Under the
    tracker, expiry doubles as the fallback-to-another-peer trigger, so
    time-sensitive types fail over quickly.
  • MAX_PEER_OBJECT_ANNOUNCEMENTS stays at 2 * MAX_INV_SZ. Upstream's
    follow-up commit reducing the cap to 5000 is deliberately skipped:
    governance vote sync legitimately announces up to MAX_INV_SZ objects
    from a single peer.
  • ReceivedResponse() returns bool (upstream: void): whether it completed a
    CANDIDATE/REQUESTED announcement. This implements the pre-existing
    PeerConsumeObjectRequest consume-once semantics that governance uses to
    reject unsolicited objects, without a separate non-atomic lookup.
  • ForgetTxHash() takes a CInv and only deletes announcements of that exact
    inv. Transaction paths forget both MSG_TX and MSG_DSTX for a txid;
    orphan-parent fetching announces MSG_DSTX only (a MSG_DSTX getdata is
    answered with the DSTX wrapper when available and a plain TX otherwise,
    so nothing is lost, and announcing both types would fetch twice).
  • limitedmap is removed from the request logic but not deleted (upstream
    deleted it entirely): Dash's chainlock handler still uses it.
  • The NOTFOUND misbehavior bound is announcement-based, as upstream, since
    the in-flight count is no longer hard-capped.
  • p2p_tx_download.py: Dash functional tests always run under active
    mocktime, so all delay waits use bump_mocktime instead of wall-clock
    sleeps. test_large_inv_batch and test_oversized_notfound are not ported
    (the Dash cap and NOTFOUND bound cannot be reached within a single
    protocol message).

How Has This Been Tested?

Run unit/functional tests

Breaking Changes

N/A

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone

@knst
knst force-pushed the bp-v21-p26-19988 branch from fec40b3 to 50ba7d4 Compare March 19, 2024 17:52
@github-actions

Copy link
Copy Markdown

This pull request has conflicts, please rebase.

@knst
knst marked this pull request as draft March 21, 2024 21:28
@DashCoreAutoGuix

Copy link
Copy Markdown

❌ Backport Verification - Issues Detected

Original Bitcoin commit: 89359089fd (bitcoin#19988)
Reviewed commit hash: a2459bb672-verify-1753724153

Issues found:

The Bitcoin commit shows a clean 15-file change adding transaction request logic, but this PR has extensive modifications to Dash-specific modules (governance, LLMQ, spork) that weren't part of the original Bitcoin change.

❌ File Operation Violations

Critical missing files:

  • src/net.cpp (Bitcoin modified, Dash PR missing)

Extra Dash-specific files not in Bitcoin:

  • Multiple LLMQ files (governance.cpp, chainlocks.cpp, etc.)
  • Spork system modifications
  • Extensive Makefile changes

Please address these issues:

  1. Rebase the PR to resolve conflicts
  2. Review the Bitcoin commit scope vs current PR scope
  3. Ensure all Bitcoin changes are included (especially src/net.cpp)
  4. Consider if Dash-specific adaptations are necessary or scope creep

Re-run verification after addressing these issues.
EOF < /dev/null

@knst
knst force-pushed the bp-v21-p26-19988 branch from a2459bb to 257de38 Compare July 17, 2026 18:15
@knst
knst marked this pull request as ready for review July 17, 2026 18:47
@thepastaclaw

thepastaclaw commented Jul 17, 2026

Copy link
Copy Markdown

🕓 Ready for review — 10 ahead in queue (commit 256fde9)
Queue position: 11/11

fd9a006 Report and verify expirations (Pieter Wuille)
86f50ed Delete limitedmap as it is unused now (Pieter Wuille)
cc16fff Make txid delay penalty also apply to fetches of orphan's parents (Pieter Wuille)
173a1d2 Expedite removal of tx requests that are no longer needed (Pieter Wuille)
de11b0a Reduce MAX_PEER_TX_ANNOUNCEMENTS for non-PF_RELAY peers (Pieter Wuille)
242d164 Change transaction request logic to use txrequest (Pieter Wuille)
5b03121 Add txrequest fuzz tests (Pieter Wuille)
3c7fe0e Add txrequest unit tests (Pieter Wuille)
da3b8fd Add txrequest module (Pieter Wuille)

Pull request description:

  This replaces the transaction request logic with an encapsulated class that maintains all the state surrounding it. By keeping it stand alone, it can be easily tested (using included unit tests and fuzz tests).

  The major changes are:

  * Announcements from outbound (and whitelisted) peers are now always preferred over those from inbound peers. This used to be the case for the first request (by delaying the first request from inbound peers), and a bias afters. The 2s delay for requests from inbound peers still exists, but after that, if viable outbound peers remain for any given transaction, they will always be tried first.
  * No more hard cap of 100 in flight transactions per peer, as there is less need for it (memory usage is linear in the number of announcements, but independent from the number in flight, and CPU usage isn't affected by it). Furthermore, if only one peer announces a transaction, and it has over 100 in flight already, we still want to request it from them. The cap is replaced with a rule that announcements from such overloaded peers get an additional 2s delay (possibly combined with the existing 2s delays for inbound connections, and for txid peers when wtxid peers are available).
  * The limit of 100000 tracked announcements is reduced to 5000; this was excessive. This can be bypassed using the PF_RELAY permission (to accommodate locally dumping a batch of many transactions).

  This replaces bitcoin#19184, rebased on bitcoin#18044 and with many small changes.

ACKs for top commit:
  ariard:
    Code Review ACK fd9a006. I've reviewed the new TxRequestTracker, its integration in net_processing, unit/functional/fuzzing test coverage. I looked more for soundness of new specification rather than functional consistency with old transaction request logic.
  MarcoFalke:
    Approach ACK fd9a006 🏹
  naumenkogs:
    Code Review ACK fd9a006. I've reviewed everything, mostly to see how this stuff works at the lower level (less documentation-wise, more implementation-wise), and to try breaking it with unexpected sequences of events.
  jnewbery:
    utACK fd9a006
  jonatack:
    WIP light ACK fd9a006 have read the code, verified that each commit is hygienic, e.g. debug build clean and tests green, and have been running a node on and off with this branch and grepping the net debug log. Am still unpacking the discussion hidden by GitHub by fetching it via the API and connecting the dots, storing notes and suggestions in a local branch; at this point none are blockers.
  ryanofsky:
    Light code review ACK fd9a006, looking at txrequest implementation, unit test implementation, and net_processing integration, just trying to understand how it works and looking for anything potentially confusing in the implementation. Didn't look at functional tests or catch up on review discussion. Just a sanity check review focused on:

Tree-SHA512: ea7b52710371498b59d9c9cfb5230dd544fe9c6cb699e69178dea641646104f38a0b5ec7f5f0dbf1eb579b7ec25a31ea420593eff3b7556433daf92d4b0f0dd7

Co-authored-by: Wladimir J. van der Laan <laanwj@protonmail.com>
@knst
knst force-pushed the bp-v21-p26-19988 branch from 257de38 to 256fde9 Compare July 17, 2026 18:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 257de3874b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/net_processing.cpp
const bool preferred = state->fPreferredDownload;
auto delay{0us};
if (inv.IsMsgTx() && !preferred && m_nodeman == nullptr) delay += NONPREF_PEER_TX_DELAY;
const bool overloaded = m_object_request.CountInFlight(node.GetId()) >= MAX_PEER_OBJECT_REQUEST_IN_FLIGHT;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Count queued announcements before skipping overload delay

When a peer sends a large INV batch before SendMessages() has issued any GETDATA, CountInFlight() is still 0 for every item in that batch, so none of the announcements get the overload delay. The next send loop will therefore request the whole batch at once (up to MAX_INV_SZ, and Dash keeps the per-peer announcement cap high for governance sync), whereas the previous code stopped at 100 in-flight objects. Include queued candidates in this threshold or cap the requestable set so one bulk announcement cannot immediately fan out tens of thousands of GETDATA requests to a single peer.

Useful? React with 👍 / 👎.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Introduces TxRequestTracker to manage peer announcements, request prioritization, expiration, completion, and cleanup. PeerManagerImpl now uses the tracker instead of legacy object-request state across inventory processing, GETDATA scheduling, responses, disconnects, and transaction handling. Governance, CoinJoin, and InstantSend handlers clear accepted object requests. Unit, fuzz, and functional tests cover tracker transitions and download behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • dashpay/dash#7442: Updates governance request tracking through the network-layer request tracker.

Suggested reviewers: udjinm6

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: a backport that overhauls transaction request logic.
Description check ✅ Passed The description is directly about replacing transaction request logic and adding tests, matching the changeset.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/net_processing.cpp`:
- Around line 2340-2350: Revalidate each PeerRef from peersToAsk after acquiring
cs_main and before calling m_object_request.ReceivedInv in the transaction
request flow. Skip peers that were finalized or disconnected by
FinalizeNode/DisconnectedPeer between peer-map collection and tracker
registration, so only currently registered peers become candidates.

In `@test/functional/p2p_tx_download.py`:
- Around line 199-203: Update test_spurious_notfound to synchronize processing
of the sent NOTFOUND message before the test proceeds. After peer.send_message,
use the TestP2PConn ping/barrier mechanism and wait for its completion, ensuring
message-processing or disconnect failures are surfaced before any subsequent
restart.
- Around line 100-107: Enforce the calculated node-time deadline in the
tx_in_node1_mempool polling flow: track elapsed mocktime or polling duration
against timeout and fail once timeout is exceeded, while retaining the existing
mempool check and mocktime advancement.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8e324552-fc19-4984-8f3f-5aefa3d1eeee

📥 Commits

Reviewing files that changed from the base of the PR and between ca2912d and 257de38.

📒 Files selected for processing (16)
  • src/Makefile.am
  • src/Makefile.test.include
  • src/coinjoin/server.cpp
  • src/governance/net_governance.cpp
  • src/instantsend/net_instantsend.cpp
  • src/msg_result.h
  • src/net.h
  • src/net_processing.cpp
  • src/net_processing.h
  • src/protocol.h
  • src/test/fuzz/txrequest.cpp
  • src/test/net_tests.cpp
  • src/test/txrequest_tests.cpp
  • src/txrequest.cpp
  • src/txrequest.h
  • test/functional/p2p_tx_download.py
💤 Files with no reviewable changes (1)
  • src/net.h

Comment thread src/net_processing.cpp
Comment on lines 2340 to +2350
LOCK(cs_main);
const auto current_time{GetTime<std::chrono::microseconds>()};
// Clear any COMPLETED leftovers first (they would suppress the new announcements), so the
// transaction is requested ASAP even if earlier requests for it failed.
m_object_request.ForgetTxHash(CInv(MSG_TX, txid));
m_object_request.ForgetTxHash(CInv(MSG_DSTX, txid));
for (PeerRef& peer : peersToAsk) {
LogPrintf("PeerManagerImpl::%s -- txid=%s: asking other peer %d for correct TX\n", __func__,
txid.ToString(), peer->m_id);

RequestObject(peer->m_id, inv, GetTime<std::chrono::microseconds>(), /*fForce=*/true);
m_object_request.ReceivedInv(peer->m_id, CInv(MSG_TX, txid), /*preferred=*/true, current_time);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Skip peers finalized before tracker registration.

The peer-map lock is released before cs_main is acquired, so FinalizeNode() can call DisconnectedPeer() in between. Registering that stale PeerRef afterward leaves a candidate for a nonexistent peer; if selected, it blocks live fallback peers indefinitely.

Proposed fix
         for (PeerRef& peer : peersToAsk) {
+            if (State(peer->m_id) == nullptr) continue;
             LogPrintf("PeerManagerImpl::%s -- txid=%s: asking other peer %d for correct TX\n", __func__,
                       txid.ToString(), peer->m_id);

             m_object_request.ReceivedInv(peer->m_id, CInv(MSG_TX, txid), /*preferred=*/true, current_time);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
LOCK(cs_main);
const auto current_time{GetTime<std::chrono::microseconds>()};
// Clear any COMPLETED leftovers first (they would suppress the new announcements), so the
// transaction is requested ASAP even if earlier requests for it failed.
m_object_request.ForgetTxHash(CInv(MSG_TX, txid));
m_object_request.ForgetTxHash(CInv(MSG_DSTX, txid));
for (PeerRef& peer : peersToAsk) {
LogPrintf("PeerManagerImpl::%s -- txid=%s: asking other peer %d for correct TX\n", __func__,
txid.ToString(), peer->m_id);
RequestObject(peer->m_id, inv, GetTime<std::chrono::microseconds>(), /*fForce=*/true);
m_object_request.ReceivedInv(peer->m_id, CInv(MSG_TX, txid), /*preferred=*/true, current_time);
LOCK(cs_main);
const auto current_time{GetTime<std::chrono::microseconds>()};
// Clear any COMPLETED leftovers first (they would suppress the new announcements), so the
// transaction is requested ASAP even if earlier requests for it failed.
m_object_request.ForgetTxHash(CInv(MSG_TX, txid));
m_object_request.ForgetTxHash(CInv(MSG_DSTX, txid));
for (PeerRef& peer : peersToAsk) {
if (State(peer->m_id) == nullptr) continue;
LogPrintf("PeerManagerImpl::%s -- txid=%s: asking other peer %d for correct TX\n", __func__,
txid.ToString(), peer->m_id);
m_object_request.ReceivedInv(peer->m_id, CInv(MSG_TX, txid), /*preferred=*/true, current_time);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/net_processing.cpp` around lines 2340 - 2350, Revalidate each PeerRef
from peersToAsk after acquiring cs_main and before calling
m_object_request.ReceivedInv in the transaction request flow. Skip peers that
were finalized or disconnected by FinalizeNode/DisconnectedPeer between peer-map
collection and tracker registration, so only currently registered peers become
candidates.

Comment on lines +100 to +107
assert self.nodes[1].getpeerinfo()[0]['inbound'] == False
timeout = 2 + NONPREF_PEER_TX_DELAY + GETDATA_TX_INTERVAL
self.log.info(f"Tx should be received at node 1 after {timeout} seconds of node time")

def tx_in_node1_mempool():
self.bump_mocktime(1)
return tx['txid'] in self.nodes[1].getrawmempool()
self.wait_until(tx_in_node1_mempool)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Enforce the calculated node-time deadline.

wait_until() may call this predicate for 60 wall-clock seconds, advancing mocktime on every poll. The test can therefore pass after substantially more than timeout node seconds, hiding relay-delay regressions.

Proposed fix
-        def tx_in_node1_mempool():
-            self.bump_mocktime(1)
-            return tx['txid'] in self.nodes[1].getrawmempool()
-        self.wait_until(tx_in_node1_mempool)
+        self.bump_mocktime(timeout)
+        self.wait_until(lambda: tx['txid'] in self.nodes[1].getrawmempool())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert self.nodes[1].getpeerinfo()[0]['inbound'] == False
timeout = 2 + NONPREF_PEER_TX_DELAY + GETDATA_TX_INTERVAL
self.log.info(f"Tx should be received at node 1 after {timeout} seconds of node time")
def tx_in_node1_mempool():
self.bump_mocktime(1)
return tx['txid'] in self.nodes[1].getrawmempool()
self.wait_until(tx_in_node1_mempool)
assert self.nodes[1].getpeerinfo()[0]['inbound'] == False
timeout = 2 + NONPREF_PEER_TX_DELAY + GETDATA_TX_INTERVAL
self.log.info(f"Tx should be received at node 1 after {timeout} seconds of node time")
self.bump_mocktime(timeout)
self.wait_until(lambda: tx['txid'] in self.nodes[1].getrawmempool())
🧰 Tools
🪛 Flake8 (7.3.0)

[error] 100-100: comparison to False should be 'if cond is False:' or 'if not cond:'

(E712)

🪛 Ruff (0.15.21)

[error] 100-100: Avoid equality comparisons to False; use not self.nodes[1].getpeerinfo()[0]['inbound']: for false checks

Replace with not self.nodes[1].getpeerinfo()[0]['inbound']

(E712)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/functional/p2p_tx_download.py` around lines 100 - 107, Enforce the
calculated node-time deadline in the tx_in_node1_mempool polling flow: track
elapsed mocktime or polling duration against timeout and fail once timeout is
exceeded, while retaining the existing mempool check and mocktime advancement.

Comment on lines 199 to +203
def test_spurious_notfound(self):
self.log.info('Check that spurious notfound is ignored')
self.nodes[0].p2ps[0].send_message(msg_notfound(vec=[CInv(1, 1)]))

def test_oversized_notfound(self):
self.log.info('Check that oversized notfound increases misbehavior score')
oversized_notfound_count = MAX_NOTFOUND_SIZE + 1
invs = [CInv(t=1, h=i) for i in range(oversized_notfound_count)]
with self.nodes[0].assert_debug_log(["Misbehaving", f"notfound message size = {oversized_notfound_count}"]):
self.nodes[0].p2ps[0].send_message(msg_notfound(vec=invs))
self.nodes[0].p2ps[0].sync_with_ping()
self.restart_node(0)
peer = self.nodes[0].add_p2p_connection(TestP2PConn())
peer.send_message(msg_notfound(vec=[CInv(MSG_TX, 1)]))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Synchronize the spurious NOTFOUND before ending the test.

send_message() does not prove that dashd processed the message before the subsequent restart. Use a ping barrier so disconnects or processing failures are observable.

Proposed fix
-        peer.send_message(msg_notfound(vec=[CInv(MSG_TX, 1)]))
+        peer.send_and_ping(msg_notfound(vec=[CInv(MSG_TX, 1)]))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_spurious_notfound(self):
self.log.info('Check that spurious notfound is ignored')
self.nodes[0].p2ps[0].send_message(msg_notfound(vec=[CInv(1, 1)]))
def test_oversized_notfound(self):
self.log.info('Check that oversized notfound increases misbehavior score')
oversized_notfound_count = MAX_NOTFOUND_SIZE + 1
invs = [CInv(t=1, h=i) for i in range(oversized_notfound_count)]
with self.nodes[0].assert_debug_log(["Misbehaving", f"notfound message size = {oversized_notfound_count}"]):
self.nodes[0].p2ps[0].send_message(msg_notfound(vec=invs))
self.nodes[0].p2ps[0].sync_with_ping()
self.restart_node(0)
peer = self.nodes[0].add_p2p_connection(TestP2PConn())
peer.send_message(msg_notfound(vec=[CInv(MSG_TX, 1)]))
def test_spurious_notfound(self):
self.log.info('Check that spurious notfound is ignored')
self.restart_node(0)
peer = self.nodes[0].add_p2p_connection(TestP2PConn())
peer.send_and_ping(msg_notfound(vec=[CInv(MSG_TX, 1)]))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/functional/p2p_tx_download.py` around lines 199 - 203, Update
test_spurious_notfound to synchronize processing of the sent NOTFOUND message
before the test proceeds. After peer.send_message, use the TestP2PConn
ping/barrier mechanism and wait for its completion, ensuring message-processing
or disconnect failures are surfaced before any subsequent restart.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR introduces TxRequestTracker, a new class managing per-peer transaction/object download request scheduling, replacing the previous per-peer ObjectDownloadState model and global request-tracking maps in net_processing.cpp. Integration points across governance, InstantSend, and CoinJoin now call PeerForgetObjectRequest when objects are accepted. A new CInv::operator== is added, and constants for delay/threshold behavior are renamed. Extensive unit and fuzz tests validate the tracker, and functional test p2p_tx_download.py is updated to match new timing constants and behaviors.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • dashpay/dash#7414: Both PRs modify NetGovernance::ProcessMessage in src/governance/net_governance.cpp to change governance request tracking behavior.
  • dashpay/dash#7442: The main PR's TxRequestTracker and PeerForgetObjectRequest plumbing directly underpins the retrieved PR's governance authorization changes.
  • dashpay/dash#7459: Both PRs modify PeerManagerImpl::SendMessages() timing/comparison logic in src/net_processing.cpp.

Suggested reviewers: udjinm6, thepastaclaw, pastapastapasta

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: overhauling transaction request logic as a backport.
Description check ✅ Passed The description is directly related and accurately explains the tracker rewrite, tests, and Dash-specific adaptations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/net_processing.cpp`:
- Around line 1547-1561: Update the TxRequestTracker preference and delay logic
around preferred and overloaded announcements: derive preferredness from the
connection’s outbound or NoBan status rather than state->fPreferredDownload, and
use IsGenTxMsg() for the transaction-delay condition so both transaction
inventory types receive the correct treatment. Preserve the existing
Dash-specific m_nodeman behavior and overload delay when calling ReceivedInv.

In `@test/functional/p2p_tx_download.py`:
- Around line 102-108: Update the tx_in_node1_mempool polling flow to enforce
the computed timeout rather than only logging it. Track the mock-time elapsed
from the start of the wait, or establish an explicit deadline, and make
wait_until fail once timeout is exceeded while preserving the existing mempool
success check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cb9f42f1-ff8c-4f41-8e6c-94b43a64487b

📥 Commits

Reviewing files that changed from the base of the PR and between ca2912d and 256fde9.

📒 Files selected for processing (16)
  • src/Makefile.am
  • src/Makefile.test.include
  • src/coinjoin/server.cpp
  • src/governance/net_governance.cpp
  • src/instantsend/net_instantsend.cpp
  • src/msg_result.h
  • src/net.h
  • src/net_processing.cpp
  • src/net_processing.h
  • src/protocol.h
  • src/test/fuzz/txrequest.cpp
  • src/test/net_tests.cpp
  • src/test/txrequest_tests.cpp
  • src/txrequest.cpp
  • src/txrequest.h
  • test/functional/p2p_tx_download.py
💤 Files with no reviewable changes (1)
  • src/net.h

Comment thread src/net_processing.cpp
Comment on lines +1547 to +1561
// Decide the TxRequestTracker parameters for this announcement:
// - "preferred": if fPreferredDownload is set (= outbound, or PF_NOBAN permission)
// - "reqtime": current time plus delays for:
// - NONPREF_PEER_TX_DELAY for announcements of transactions from non-preferred connections.
// Dash-specific object types are never delayed (they carry time-sensitive consensus data),
// and neither is anything in masternode mode.
// - OVERLOADED_PEER_OBJECT_DELAY for announcements from peers which have at least
// MAX_PEER_OBJECT_REQUEST_IN_FLIGHT requests in flight.
const bool preferred = state->fPreferredDownload;
auto delay{0us};
if (inv.IsMsgTx() && !preferred && m_nodeman == nullptr) delay += NONPREF_PEER_TX_DELAY;
const bool overloaded = m_object_request.CountInFlight(node.GetId()) >= MAX_PEER_OBJECT_REQUEST_IN_FLIGHT;
if (overloaded) delay += OVERLOADED_PEER_OBJECT_DELAY;

m_object_request.ReceivedInv(node.GetId(), inv, preferred, current_time + delay);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Preserve outbound/whitelisted priority for both transaction inventory types.

fPreferredDownload excludes peers without block-serving services, while IsMsgTx() lets inbound peers bypass the delay by announcing the same transaction as MSG_DSTX. Derive preferredness from connection/NoBan status and use IsGenTxMsg().

Proposed fix
-    const bool preferred = state->fPreferredDownload;
+    const bool preferred =
+        !node.IsInboundConn() || node.HasPermission(NetPermissionFlags::NoBan);
     auto delay{0us};
-    if (inv.IsMsgTx() && !preferred && m_nodeman == nullptr) delay += NONPREF_PEER_TX_DELAY;
+    if (inv.IsGenTxMsg() && !preferred && m_nodeman == nullptr) {
+        delay += NONPREF_PEER_TX_DELAY;
+    }

As per coding guidelines, preserve Dash-specific behavior when modifying or backporting Bitcoin Core C++ paths. <coding_guidelines>

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Decide the TxRequestTracker parameters for this announcement:
// - "preferred": if fPreferredDownload is set (= outbound, or PF_NOBAN permission)
// - "reqtime": current time plus delays for:
// - NONPREF_PEER_TX_DELAY for announcements of transactions from non-preferred connections.
// Dash-specific object types are never delayed (they carry time-sensitive consensus data),
// and neither is anything in masternode mode.
// - OVERLOADED_PEER_OBJECT_DELAY for announcements from peers which have at least
// MAX_PEER_OBJECT_REQUEST_IN_FLIGHT requests in flight.
const bool preferred = state->fPreferredDownload;
auto delay{0us};
if (inv.IsMsgTx() && !preferred && m_nodeman == nullptr) delay += NONPREF_PEER_TX_DELAY;
const bool overloaded = m_object_request.CountInFlight(node.GetId()) >= MAX_PEER_OBJECT_REQUEST_IN_FLIGHT;
if (overloaded) delay += OVERLOADED_PEER_OBJECT_DELAY;
m_object_request.ReceivedInv(node.GetId(), inv, preferred, current_time + delay);
// Decide the TxRequestTracker parameters for this announcement:
// - "preferred": if fPreferredDownload is set (= outbound, or PF_NOBAN permission)
// - "reqtime": current time plus delays for:
// - NONPREF_PEER_TX_DELAY for announcements of transactions from non-preferred connections.
// Dash-specific object types are never delayed (they carry time-sensitive consensus data),
// and neither is anything in masternode mode.
// - OVERLOADED_PEER_OBJECT_DELAY for announcements from peers which have at least
// MAX_PEER_OBJECT_REQUEST_IN_FLIGHT requests in flight.
const bool preferred =
!node.IsInboundConn() || node.HasPermission(NetPermissionFlags::NoBan);
auto delay{0us};
if (inv.IsGenTxMsg() && !preferred && m_nodeman == nullptr) {
delay += NONPREF_PEER_TX_DELAY;
}
const bool overloaded = m_object_request.CountInFlight(node.GetId()) >= MAX_PEER_OBJECT_REQUEST_IN_FLIGHT;
if (overloaded) delay += OVERLOADED_PEER_OBJECT_DELAY;
m_object_request.ReceivedInv(node.GetId(), inv, preferred, current_time + delay);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/net_processing.cpp` around lines 1547 - 1561, Update the TxRequestTracker
preference and delay logic around preferred and overloaded announcements: derive
preferredness from the connection’s outbound or NoBan status rather than
state->fPreferredDownload, and use IsGenTxMsg() for the transaction-delay
condition so both transaction inventory types receive the correct treatment.
Preserve the existing Dash-specific m_nodeman behavior and overload delay when
calling ReceivedInv.

Source: Coding guidelines

Comment on lines +102 to +108
timeout = 2 + NONPREF_PEER_TX_DELAY + GETDATA_TX_INTERVAL
self.log.info("Tx should be received at node 1 after {} seconds of node time".format(timeout))

def tx_in_node1_mempool():
self.bump_mocktime(1)
return tx['txid'] in self.nodes[1].getrawmempool()
self.wait_until(tx_in_node1_mempool)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Enforce the computed mock-time deadline.

timeout is only logged; each poll advances mocktime without checking that bound. The test can pass after far more simulated time than advertised, missing relay-delay regressions. Fail after timeout mock-time increments or compare against an explicit mock-time deadline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/functional/p2p_tx_download.py` around lines 102 - 108, Update the
tx_in_node1_mempool polling flow to enforce the computed timeout rather than
only logging it. Track the mock-time elapsed from the start of the wait, or
establish an explicit deadline, and make wait_until fail once timeout is
exceeded while preserving the existing mempool success check.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants