fix: Derive traversal node IDs from the branch actually descended - #7942
Draft
bthomee wants to merge 1 commit into
Draft
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens SHAMap traversal correctness by making it impossible to associate a node with a mismatched SHAMapNodeID during traversals (and related walks), and adds targeted traversal tests to exercise deep/leaf-depth paths that previously weren’t covered.
Changes:
- Introduces
NodePathStackto derive node IDs from the actual descended branch (and assert key/ID consistency) rather than accepting caller-supplied IDs. - Refactors traversal/walk code paths (
walkTowardsKey,belowHelper, iterator/bounds traversal) to useNodePathStack. - Adds new traversal-focused unit tests, including leaf-depth fan-out and inner-node collapse scenarios.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/tests/libxrpl/shamap/SHAMap.cpp | Adds traversal/bounds/iteration tests covering deep and leaf-depth traversal scenarios, including deletion/collapse cases. |
| src/libxrpl/shamap/SHAMapSync.cpp | Switches proof-path collection walk to use the new NodePathStack. |
| src/libxrpl/shamap/SHAMap.cpp | Refactors traversal helpers and bounds/iteration logic to build consistent node/ID paths via NodePathStack. |
| include/xrpl/shamap/SHAMap.h | Defines NodePathStack and updates internal method/iterator plumbing to use it. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+325
to
+326
| map.addItem(SHAMapNodeType::TnAccountState, makeShamapitem(k, std::move(vuc))); | ||
| map.invariants(); |
bthomee
force-pushed
the
bthomee/shamap-stack-04-clamp-selectbranch-depth
branch
from
August 3, 2026 23:25
2bed752 to
ed28138
Compare
bthomee
force-pushed
the
bthomee/shamap-stack-05-derive-traversal-node-ids
branch
from
August 3, 2026 23:25
199fe0d to
651f082
Compare
bthomee
force-pushed
the
bthomee/shamap-stack-04-clamp-selectbranch-depth
branch
from
August 4, 2026 00:12
ed28138 to
7b7d835
Compare
bthomee
force-pushed
the
bthomee/shamap-stack-05-derive-traversal-node-ids
branch
from
August 4, 2026 00:12
651f082 to
fcbd9fe
Compare
`belowHelper` built each stack entry's `SHAMapNodeID` from `branch`, the branch used to reach the subtree root, rather than `childBranch`, the branch it had just descended. The resulting IDs carried a correct depth but named a different subtree, and nothing rejected them: such an ID has a legal depth and a legal mask, so only comparing it against an actual leaf key exposes the mismatch. The affected stacks feed read-only traversals whose consumers use only the depth, so no ledger state, hash, or peer message was affected, but any future consumer of `getNodeID()` would have silently received the wrong position. Rather than fix the one call, make the mistake unrepresentable. `NodePathStack` replaces the bare `std::stack` and refuses to accept an ID at all: every push takes the branch being descended and derives the ID itself, so a node and its ID cannot disagree. `isPrefixOf` assertions on each push catch a wrong branch at the point it happens rather than wherever the ID is later read. Leaf entries now keep the depth they were reached at instead of a normalized `kLeafDepth`, which is what lets those assertions hold: `addGiveItem` splits a leaf from the depth it actually sits at. The new traversal tests fail on the previous code: reverting the branch derivation trips the leaf-key assertion on the first iteration. Also adds a `deepFanOutKeysAtLeafDepth` helper and mirrors them against it, since the existing `deepFanOutKeys`'s fan-out at the 6th nibble keeps its tree only about 6 levels deep and never exercised the depth-63/64 code these tests are meant to protect, plus a case that collapses the entire depth-63 chain of single-child inner nodes into a leaf on the final delete, which the every-other-key deletion pattern the other new tests use never triggers.
bthomee
force-pushed
the
bthomee/shamap-stack-04-clamp-selectbranch-depth
branch
from
August 4, 2026 00:49
7b7d835 to
a09fec0
Compare
bthomee
force-pushed
the
bthomee/shamap-stack-05-derive-traversal-node-ids
branch
from
August 4, 2026 00:49
fcbd9fe to
6318ca4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 5/8 of a stack. Base: part 4 (
bthomee/shamap-stack-04-clamp-selectbranch-depth).belowHelperbuilt each stack entry'sSHAMapNodeIDfrombranch, the branchused to reach the subtree root, rather than
childBranch, the branch it hadjust descended. The resulting IDs carried a correct depth but named a
different subtree, and nothing rejected them: such an ID has a legal depth and
a legal mask, so only comparing it against an actual leaf key exposes the
mismatch. The affected stacks feed read-only traversals whose consumers use
only the depth, so no ledger state, hash, or peer message was affected, but
any future consumer of
getNodeID()would have silently received the wrongposition.
Rather than fix the one call, make the mistake unrepresentable.
NodePathStackreplaces the barestd::stackand refuses to accept an ID atall: every push takes the branch being descended and derives the ID itself, so
a node and its ID cannot disagree.
isPrefixOfassertions on each push catch awrong branch at the point it happens rather than wherever the ID is later
read. Leaf entries now keep the depth they were reached at instead of a
normalized
kLeafDepth, which is what lets those assertions hold:addGiveItemsplits a leaf from the depth it actually sits at.The new traversal tests fail on the previous code: reverting the branch
derivation trips the leaf-key assertion on the first iteration. Also adds a
deepFanOutKeysAtLeafDepthhelper and mirrors them against it, since theexisting
deepFanOutKeys's fan-out at the 6th nibble keeps its tree onlyabout 6 levels deep and never exercised the depth-63/64 code these tests are
meant to protect, plus a case that collapses the entire depth-63 chain of
single-child inner nodes into a leaf on the final delete, which the
every-other-key deletion pattern the other new tests use never triggers.