fix: Keep a settled acquisition registered so late replies stay bounded - #8099
Conversation
There was a problem hiding this comment.
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
TransactionAcquiretest that drives late replies throughInboundTransactions::gotData()and the asyncdone() -> 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.
d334202 to
0cafffc
Compare
0cafffc to
e81b1a1
Compare
e81b1a1 to
cad6d0e
Compare
|
This PR has conflicts, please resolve them in order for the PR to be reviewed. |
1 similar comment
|
This PR has conflicts, please resolve them in order for the PR to be reviewed. |
cad6d0e to
bb4d551
Compare
|
All conflicts have been resolved. Assigned reviewers can now start or resume their review. |
bb4d551 to
bde2683
Compare
|
This PR has conflicts, please resolve them in order for the PR to be reviewed. |
2 similar comments
|
This PR has conflicts, please resolve them in order for the PR to be reviewed. |
|
This PR has conflicts, please resolve them in order for the PR to be reviewed. |
|
All conflicts have been resolved. Assigned reviewers can now start or resume their review. |
|
This PR has conflicts, please resolve them in order for the PR to be reviewed. |
bde2683 to
d886d01
Compare
|
All conflicts have been resolved. Assigned reviewers can now start or resume their review. |
d886d01 to
b8108c0
Compare
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.
b8108c0 to
117a896
Compare
There was a problem hiding this comment.
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.
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 acquisitionitself 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'sacquirepointer unconditionally once a set arrived, so anyreply for that hash arriving after completion took
gotData()'sta == nullptrbranch from then on —charged outright, with no allowance in play at all, since
takeNodesLocked()was never reached to applyone. The tests added for the allowance in part 14 called
acquire->takeNodes()directly, bypassinggotData()/giveSet()entirely, so they never exercised this gap: the real production entry point forpeer replies never got as far as the allowance at all.
giveSet()now only resets the entry'sacquirepointer when something other than the acquisitionitself 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()sweepsthe 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 foldedin: 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
TransactionAcquiregtest/boost case driven end to end throughInboundTransactions::gotData(), including the async jobdone()hands off to, so the real completionpath is what proves the allowance survives it.
API Impact
None of the checkboxes below apply: this is an internal
xrpldcorrectness fix with nolibxrpl,public-API, or peer-protocol surface.