Skip to content

fix: Keep a settled acquisition registered so late replies stay bounded - #8099

Open
bthomee wants to merge 1 commit into
bthomee/shamap-invalid-15-invalidated-map-receivenodefrom
bthomee/shamap-invalid-16-late-reply-survives-giveset
Open

fix: Keep a settled acquisition registered so late replies stay bounded#8099
bthomee wants to merge 1 commit into
bthomee/shamap-invalid-15-invalidated-map-receivenodefrom
bthomee/shamap-invalid-16-late-reply-survives-giveset

Conversation

@bthomee

@bthomee bthomee commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Part 16/17 of a stack. Base: part 15 (bthomee/shamap-invalid-15-invalidated-map-receivenode).

High Level Overview of Change

InboundTransactions::giveSet() now keeps a completed acquisition registered when the acquisition
itself is what supplied the set, so a late reply for that hash still reaches the late-reply allowance
instead of being charged outright.

Context of Change

giveSet() used to reset the map entry's acquire pointer unconditionally once a set arrived, so any
reply for that hash arriving after completion took gotData()'s ta == nullptr branch from then on —
charged outright, with no allowance in play at all, since takeNodesLocked() was never reached to apply
one. The tests added for the allowance in part 14 called acquire->takeNodes() directly, bypassing
gotData()/giveSet() entirely, so they never exercised this gap: the real production entry point for
peer replies never got as far as the allowance at all.

giveSet() now only resets the entry's acquire pointer when something other than the acquisition
itself supplied the set — a set arriving some other way still cancels an acquisition genuinely in
flight, but the acquisition completing on its own is not that. Keeping it alive until newRound() sweeps
the entry is what lets a late reply for this hash still reach getAcquire() and, through it,
takeNodesLocked()'s allowance.

This is a Copilot review finding on part 14 (#8093), split out into its own branch rather than folded
in: unlike the late-reply allowance bound fixed there, this gap is unchanged pre-existing behavior, not
something this stack makes worse, so there is no urgency tying it to that PR's release.

Covered by a new TransactionAcquire gtest/boost case driven end to end through
InboundTransactions::gotData(), including the async job done() hands off to, so the real completion
path is what proves the allowance survives it.

API Impact

None of the checkboxes below apply: this is an internal xrpld correctness fix with no libxrpl,
public-API, or peer-protocol surface.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a correctness gap in InboundTransactions::giveSet() where a completed TransactionAcquire was being unregistered immediately, causing late peer replies for the same hash to bypass TransactionAcquire::takeNodesLocked() and be charged outright without applying the “one free per peer asked” late-reply allowance.

Changes:

  • Make InboundTransactions::giveSet() only reset/cancel the tracked acquisition when the set was supplied by something other than the acquisition itself (!fromAcquire).
  • Add an end-to-end TransactionAcquire test that drives late replies through InboundTransactions::gotData() and the async done() -> giveSet() job path to verify the allowance survives completion.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/xrpld/app/ledger/detail/InboundTransactions.cpp Preserve the acquisition pointer when giveSet() is invoked by the acquisition completing, so late replies still reach allowance logic instead of ta == nullptr charging.
src/test/app/TransactionAcquire_test.cpp Adds an end-to-end regression test ensuring late-reply allowance remains effective after giveSet() runs via the async completion path.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@bthomee
bthomee force-pushed the bthomee/shamap-invalid-16-late-reply-survives-giveset branch from d334202 to 0cafffc Compare August 24, 2026 15:00
@bthomee
bthomee force-pushed the bthomee/shamap-invalid-16-late-reply-survives-giveset branch from 0cafffc to e81b1a1 Compare August 24, 2026 15:24
@bthomee
bthomee force-pushed the bthomee/shamap-invalid-16-late-reply-survives-giveset branch from e81b1a1 to cad6d0e Compare August 25, 2026 15:02
@github-actions

Copy link
Copy Markdown

This PR has conflicts, please resolve them in order for the PR to be reviewed.

1 similar comment
@github-actions

Copy link
Copy Markdown

This PR has conflicts, please resolve them in order for the PR to be reviewed.

@bthomee
bthomee force-pushed the bthomee/shamap-invalid-16-late-reply-survives-giveset branch from cad6d0e to bb4d551 Compare August 25, 2026 19:36
@github-actions

Copy link
Copy Markdown

All conflicts have been resolved. Assigned reviewers can now start or resume their review.

@bthomee
bthomee force-pushed the bthomee/shamap-invalid-16-late-reply-survives-giveset branch from bb4d551 to bde2683 Compare August 25, 2026 19:48
@github-actions

Copy link
Copy Markdown

This PR has conflicts, please resolve them in order for the PR to be reviewed.

2 similar comments
@github-actions

Copy link
Copy Markdown

This PR has conflicts, please resolve them in order for the PR to be reviewed.

@github-actions

Copy link
Copy Markdown

This PR has conflicts, please resolve them in order for the PR to be reviewed.

@github-actions

Copy link
Copy Markdown

All conflicts have been resolved. Assigned reviewers can now start or resume their review.

@github-actions

Copy link
Copy Markdown

This PR has conflicts, please resolve them in order for the PR to be reviewed.

@bthomee
bthomee force-pushed the bthomee/shamap-invalid-16-late-reply-survives-giveset branch from bde2683 to d886d01 Compare August 25, 2026 21:08
@github-actions

Copy link
Copy Markdown

All conflicts have been resolved. Assigned reviewers can now start or resume their review.

@bthomee
bthomee force-pushed the bthomee/shamap-invalid-16-late-reply-survives-giveset branch from d886d01 to b8108c0 Compare August 25, 2026 21:58
@bthomee
bthomee marked this pull request as ready for review August 26, 2026 00:21
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

giveSet() resets the map entry's acquire pointer unconditionally once a
set arrives, so any reply for that hash arriving after completion takes
gotData()'s ta == nullptr branch - charged outright, since
takeNodesLocked() is never reached to apply the late-reply allowance. The
tests for that allowance call acquire->takeNodes() directly, bypassing
gotData()/giveSet() entirely, so none of them exercise this: the real
production entry point for peer replies never reaches the allowance at
all.

Only reset the entry's acquire pointer when something other than the
acquisition itself supplied the set: a set arriving some other way still
cancels an acquisition genuinely in flight, but the acquisition completing
on its own is not that. Keeping it alive until newRound() sweeps the entry
lets a late reply for this hash still reach getAcquire() and, through it,
takeNodesLocked()'s allowance.

Addresses Copilot review feedback on PR #8093, split out into its own
branch: unlike the late-reply allowance bound in branch 14, this gap is
unchanged pre-existing behavior, not something this stack makes worse, so
there is no urgency tying it to that PR's release.
@bthomee
bthomee requested a review from a team as a code owner August 29, 2026 00:18
@bthomee
bthomee force-pushed the bthomee/shamap-invalid-16-late-reply-survives-giveset branch from b8108c0 to 117a896 Compare August 29, 2026 00:18

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change fixes a real gap: giveSet() previously reset the map entry's acquire pointer unconditionally, which meant any late reply for a hash whose acquisition had just completed skipped the late-reply allowance in takeNodesLocked() entirely and was charged outright. The fix (if (!fromAcquire) inboundSet.acquire.reset();) only tears down the acquire pointer when something other than the acquisition itself supplied the set, letting a genuinely-still-in-flight acquisition still get cancelled by a set arriving another way, while a self-completed acquisition stays registered until newRound() sweeps it. The new gtest case drives this end-to-end through gotData()/done(), which is the right way to prove the allowance is actually reachable via the real peer-reply path rather than the direct takeNodes() calls used elsewhere in the suite, and it explicitly covers the free late reply followed by a charged replay. I don't see a correctness problem in what's shown here; the one open question — whether a completed-but-not-yet-swept acquisition could accumulate under sustained load before newRound() runs — is inherent to the described design and not something this diff makes worse, so I'm not flagging it.

@bthomee bthomee added this to the 3.5.0 milestone Sep 1, 2026
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.

2 participants