Commit Graph

14376 Commits

Author SHA1 Message Date
Bart
e41e469e02 perf: Move entries off NodePathStack instead of copying them
Five sites copied the top entry out and then popped it. `SHAMapTreeNodePtr` is
refcounted, so each copy bumped its atomic strong-ref count; `release()` moves
the pointer out instead, a plain pointer swap with no atomic. `SHAMapNodeID`
also derives from `CountedObject`, but `CountedObject` declares no move
constructor, so moving a `SHAMapNodeID` still runs its copy constructor;
`release()` saves nothing on the ID half of the pair. `dirtyUp` and `delItem`
walk up to 64 levels per insert or delete on the ledger write path, so this
removes up to 64 atomic operations per call, not 128. The two sites that read
without popping now bind a reference rather than copying.

`dirtyUp` and `delItem` both drop a `dynamicPointerCast`/null check for a
static cast, on the reasoning that by the time either receives the stack,
`addGiveItem`/`updateGiveItem` have already consumed the terminal leaf entry
via `release()`, so every remaining entry is provably an inner node. That
reasoning holds today, but an `XRPL_ASSERT` is a no-op under `NDEBUG`, so
both get a real `UNREACHABLE`-guarded check instead: a release build that
somehow violated the invariant would otherwise write through a
misinterpreted node via `setChild`, silent memory corruption in place of the
clean crash `dynamicPointerCast` used to produce. `updateGiveItem`'s own
cast needed the same treatment for a different reason: an absent tag leaves
an inner node on top of the stack, and the cast that followed the assertion
there would have reinterpreted an `SHAMapInnerNode` as a `SHAMapLeafNode`.
Replaced with `if (!top->isLeaf()) return false;`, pinned by a regression
test.

The `std::move` these sites previously applied to
`staticPointerCast`/`dynamicPointerCast` was dropped rather than fixed: both
only had a `TT const&` overload, so the move bound to that const ref and
copied anyway, silently defeating the `SHAMapTreeNodePtr` refcount saving
described above. Adds the missing rvalue overload to each, tied to
`SharedIntrusive<TT>&&` rather than a bare `TT&&` so it cannot also bind to
an lvalue in preference to the const-ref overload, and restores `std::move`
at the three call sites that own a soon-to-be-discarded pointer.
2026-08-18 10:20:12 -04:00
Bart
ee6ddfbfdb fix: Make NodePathStack fail closed when assertions are compiled out
The stack asserted its preconditions and then went ahead regardless. Asserts
expand to `assert`, so in a release build every one of those was a no-op in
front of the operation it was guarding: reading or popping an empty
`std::stack` is undefined, `pushChild`'s out-of-range branch check was missing
entirely, and `getChildNodeID` throws `std::logic_error` at leaf depth. None
of these conditions are reachable through any of `SHAMap`'s public entry
points today, but the failure modes if they ever did happen would be
disproportionate: an out-of-range branch would silently corrupt a node ID
instead of failing loudly, and a `logic_error` reaching an unguarded call
chain would abort the process, since nothing in this codebase catches it.

Pushes now return false instead of throwing or silently corrupting the ID, and
reads degrade to a null node rather than undefined behavior. `[[nodiscard]]`
makes an unchecked push a compile error. Each new guard is marked
`UNREACHABLE` rather than left implicitly untested, since no test fixture in
this suite can reach these paths without building a deliberately corrupt map.
`pushRoot` gets the same conversion as every sibling method; it was the one
push still asserting instead of returning false.

`walkTowardsKey`'s two modes (with and without a caller-supplied stack) must
fail at the same node and leave the stack in a state every caller already
knows how to handle; on failure the stack is now cleared via a restored
`clear()`, and a restored `pushCurrent` lambda keeps the loop-entry and
post-loop push-and-clear logic from being duplicated. It also stops deriving
each node ID twice: a caller-supplied stack now reads the ID `pushNode` just
computed off `stack->top().second`, instead of a redundant local copy that
additionally went stale once the loop exited. `belowHelper` and
`peekNextItem` finally get the fallback `top()`'s own docstring promises: both
read `stack.top()` right after an assert-only emptiness check, with no
fallback for release builds, so both now return early on an empty stack
instead of dereferencing a null `SHAMapTreeNodePtr`.

`pushChild`'s hard guard also only checked the parent's depth against
`kLeafDepth`, one level too permissive for an inner child: a parent at 63
passed the check, then pushed an inner child at 64 with only a debug-only
assert catching it, the exact gap this commit exists to close. Tightened to
require depth + 1 below `kLeafDepth` for an inner child, with
`walkTowardsKey`'s no-stack path given the identical tightening so a
malformed map fails at the same node in both modes.
2026-08-18 10:19:55 -04:00
Bart
0f0b8fc650 refactor: Unify upperBound and lowerBound into boundHelper
The two functions were near duplicates: walk to the key, then look for the
nearest leaf on one side. Only the scan direction, the comparison deciding a
leaf qualifies, and whether to take the first or last leaf below the subtree
differed, exactly the distinction `BelowDirection` already draws for
`belowHelper`, so the pair collapse into one parameterised walk. Also drops
the stale `// TODO: what to return here?` above `lowerBound`'s `return end()`:
no predecessor is the correct answer for the smallest key, and the tests pin
it.

Existing coverage only exercised `boundHelper`'s inner-node branch, every test
map had at least three items, so the root was always an inner node and the
leaf branch at the top of the function was never reached with a real answer to
give. Adds coverage for a single-item map, where the leaf branch alone decides
the outcome, and an empty map, where the scan must find nothing on every
branch before falling through to `end()`.

Fixes the single-item test's own comment, which claimed `root_` becomes a
leaf, when in fact `root_` stays the inner node it was constructed with for
any map built via `addItem`; only a single-item map synced from a peer
(`addRootNode`) ever replaces `root_` with a leaf directly.
2026-08-18 10:19:42 -04:00
Bart
7ead12d572 fix: Derive traversal node IDs from the branch actually descended
`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.
2026-08-18 10:19:28 -04:00
Bart
6f0f065505 fix: Clamp the depth used to index selectBranch's key byte
`selectBranch` reads the key byte at `depth / 2`, which is out of bounds for a
32-byte key once the depth reaches 64. Every branch selection in the map
funnels through here, so this is the one place a stray depth can turn into a
bad read. Assert the precondition for callers, then clamp anyway: a wrong
answer for an input that should never occur is better than reading past the
buffer. Verified under ASan that the unclamped form reads one byte past a
32-byte allocation while the clamped form does not.

`depthMask`'s own 65-entry table had the same exposure one level up, reachable
through the public `createID` factory rather than only from inside the map. A
depth past `kLeafDepth` indexed that table out of bounds, confirmed under ASan
as a 4-byte `global-buffer-overflow` immediately after `kMasks`. Both places
that can set a depth now clamp it: the constructor, which is the single point
every `SHAMapNodeID`'s `depth_` passes through, and `createID`, which needs its
own bound because it picks the mask while evaluating the constructor's
argument, before the constructor body could correct anything.

Clamping rather than throwing, which is what `getChildNodeID` does for the
analogous case: `createID` is reached from `getSHAMapNodeID` with a
peer-supplied depth, and two of that function's three callers
(`InboundTransactions::gotData`, `PeerImp::onMessage`) sit on paths with no
handler between them and a thread boundary, so a throw there would end the
process rather than the message. Clamping also has to fix up `id_` alongside
`depth_`, since a node ID whose id and depth disagree fails the invariant every
read of `id_` relies on. Leaving the depth unclamped would additionally let
`getRawString` narrow it to a byte, turning depth 256 into a node claiming to
be the root.

`deserializeSHAMapNodeID` gets the same mask check `isPrefixOf` already
performs, pulled into a shared `isPrefixOfAtDepth` helper, and the masking both
it and `createID` perform is now a named `maskedToDepth` rather than a repeated
bitwise-and.

Tests cover the depth-sensitivity of `isPrefixOf`, the guards that must hold
with asserts stripped, that `deserializeSHAMapNodeID` rejects an out-of-range
depth, and the clamp itself under both build configurations (`EXPECT_DEATH`
in a forked process when the assert is live, and the clamped result compared
against depth 63 when it is not). The clamp test also gates on
`ENABLE_VOIDSTAR`, not just `NDEBUG`: under Antithesis instrumentation
`XRPL_ASSERT` routes to a handler that records the hit but never aborts, so a
Debug build with voidstar enabled has `NDEBUG` undefined yet still hits the
same non-fatal assert as a release build, and without this gate would send
that configuration into the `EXPECT_DEATH` arm, where the forked child never
dies and the test fails, breaking the CI job that runs this suite under
`-Dvoidstar=ON`.
2026-08-18 10:18:59 -04:00
Bart
d854982fd7 fix: Reject an inner node claimed at leaf depth in verifyProofPath
A SHAMap has 65 levels, and nibbles run out at level 64: `selectBranch` indexes
the key byte at `depth / 2`, so at depth 64 it reads byte 32 of a 32-byte key.
Only the leaf terminating a path may sit at that depth, but proof path nodes
come off the wire, so a peer could send 65 hash-chained inner nodes and drive
that read past the end of the buffer.

The existing length bound cannot be tightened to catch this: a path for two
keys sharing all 63 leading nibbles legitimately holds 64 inner nodes plus a
leaf, so 65 elements is valid. The claimed node type at the final depth is the
thing to reject, using `>=` rather than `==` to match the convention every
other `kLeafDepth` comparison in this codebase already follows.

Reachable from `TMProofPathResponse` via `LedgerReplayMsgHandler`; confirmed
under ASan with asserts compiled out that the unguarded read lands one byte
past a 32-byte heap allocation. Also closes a second gap: nothing checked the
terminal leaf's own key against `key`, so the hash chain alone let a peer
substitute any leaf whose subtree hashes matched at every level above it.
Pinned by tests: the 65-element path that must verify for both keys sharing
the deep prefix, and the forged all-inner path that must not.

Also guards `visitDifferences` against an inner node claimed at leaf depth:
`hasLeafNode` only checked the comparison map, not the map being walked, so a
corrupt node in the map under `visitDifferences` itself could still throw
uncaught. Skip such a node's children instead.
2026-08-18 10:17:53 -04:00
Bart
ca39bff3c8 refactor: Add SHAMapNodeID::isPrefixOf (#7939)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-08-18 12:35:32 +00:00
Vito Tumas
dd0edc19a0 fix: Conserve funds correctly when LoanPay fee payee is below reserve (#7843) 2026-08-18 11:09:33 +00:00
Copilot
820ca5b332 refactor: Convert boost::beast::string_view to std::string_view (#6306)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
Co-authored-by: Timur Yalymov <36795566+tyalymov@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
2026-08-17 23:19:56 +00:00
Gregory Tsipenyuk
1b226c8b2e perf: Optimize MPT freeze checks to reduce redundant state reads (#7411)
Co-authored-by: Chenna Keshava B S <21219765+ckeshava@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-17 21:15:16 +00:00
Gregory Tsipenyuk
ca6121c5b3 feat: Enforce MPT CanTransfer on AMM LPTokens transfers (#7418) 2026-08-17 20:58:46 +00:00
Gregory Tsipenyuk
c49789086a fix: Extend locked-MPToken unauthorize check to fixCleanup3_4_0 (#8004) 2026-08-17 12:52:20 +00:00
Bart
5337d028a2 refactor: Use unsigned int for branch-related operations (#7938)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 10:07:14 +00:00
Ed Hennis
43d842926a refactor: Rewrite Transactor::operator() to early return (#8003) 2026-08-14 20:18:33 +00:00
Bart
2adffaef72 refactor: Remove support for protocol version 2.1 (#7432)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 15:36:47 +00:00
Ayaz Salikhov
bd87edfc75 test: Check versioned tools in check-tools & print nicely (#8030) 2026-08-14 14:07:55 +00:00
Mayukha Vadari
d34aa37b3c refactor: Use std::format instead of boost::format where it fits (#7996)
Co-authored-by: Timur Yalymov <36795566+tyalymov@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-08-14 13:49:08 +00:00
Ayaz Salikhov
a0074f83d3 build: Fix versioned tools for exec wrappers (#8027) 2026-08-14 10:06:49 +00:00
Ayaz Salikhov
028ccea7a1 build: Add curl to packaging images (#8024) 2026-08-13 17:48:35 +00:00
Pratik Mankawde
df85d43d8a test: Make Drop50 message drop deterministic in LedgerReplayer test (#7964)
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2026-08-13 16:54:58 +00:00
Jingchen
8e9b1791c5 feat: Add a new closed ended vault to extend SAV (#7921)
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
2026-08-12 17:07:43 +00:00
Ayaz Salikhov
946827b9bd build: Respect lld linker if it gets auto-selected (#8011) 2026-08-12 12:11:28 -04:00
Vito Tumas
91360c5126 test: Fix LoanBatch broker cover rates and schedule overflow (#7967) 2026-08-12 12:11:28 -04:00
Timur Yalymov
af36890c11 test: Verify private-vault DEX permissions survive domain loss (#7937) 2026-08-12 12:11:28 -04:00
Timur Yalymov
1281c7a222 refactor: Drop unnecessary associateAsset calls from loan delete paths (#7986)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 12:10:17 -04:00
Copilot
153b7839a7 refactor: Replace boost::filesystem with std::filesystem across the codebase (#7012)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: mathbunnyru <12270691+mathbunnyru@users.noreply.github.com>
2026-08-12 13:40:39 +00:00
Gregory Tsipenyuk
26cc683ec1 fix: Assorted MPT/DEX fixes (#7299)
Co-authored-by: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com>
2026-08-11 18:15:51 +00:00
Mayukha Vadari
6ca2fb84d4 refactor: Replace Boost trim and to_lower with libxrpl helpers (#7995) 2026-08-11 18:15:35 +00:00
klemenfn
a3147740f2 build: Fix GCC 14 compilation (#7981)
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-08-11 13:24:56 +00:00
luisfernandomendozav
d43e5acaa7 fix: Validate account/ident type in gateway_balances (#7655) 2026-08-11 13:23:07 +00:00
Ayaz Salikhov
c74724a719 build: Reimagine linker warnings in different scenarios (#7974) 2026-08-11 12:44:01 +00:00
Alex Kremer
0a572833ea chore: Gtest migration followups second pass (#7888) 2026-08-11 12:38:40 +00:00
Chenna Keshava B S
639943123c fix: Validate buy/sell flag in nft RPC input (#7725) 2026-08-11 00:49:02 +00:00
Bryan
909cc5bba9 fix: Prevent silent zero AMM clawbacks due to integer MPT rounding (#7704)
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-08-10 21:37:53 +00:00
Peter Chen
6f5de9067a chore: Mark unreachable branches in Confidential Transfer with UNREACHABLE (#7903) 2026-08-10 21:37:38 +00:00
Kassaking7
60291c3ed6 fix: Allow OverrideFreeze to bypass individual/deep freeze on AMM trust lines (#6959) 2026-08-10 21:34:28 +00:00
Braedon Klock
4173f7e499 fix: Validate account_lines peer field type (#7728)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-10 21:30:06 +00:00
Ayaz Salikhov
9c292fbe4f build: Install conan configuration/profiles inside Nix devshell (#7997) 2026-08-10 17:49:29 +00:00
yinyiqian1
b19c3c64f2 fix: Add zero keylet check in credential (#7971) 2026-08-10 17:47:16 +00:00
Mayukha Vadari
a0e1e578a0 refactor: Remove operator!= overloads that C++20 synthesizes (#7994) 2026-08-10 17:23:02 +00:00
Mayukha Vadari
07aa97fda4 test: Use std::string::starts_with/ends_with instead of Boost (#7992) 2026-08-10 17:22:40 +00:00
Mayukha Vadari
4f8819565a fix: Assorted cleanup fixes (#7988) 2026-08-10 17:18:22 +00:00
Mayukha Vadari
6580b200db refactor: Replace boost::lexical_cast with existing alternatives (#7991) 2026-08-10 17:10:18 +00:00
Mayukha Vadari
2967f1f0cc chore: Remove unreferenced legacy documents (#7989) 2026-08-10 17:06:29 +00:00
Mayukha Vadari
71e972cbed refactor: Act on TODOs that are unblocked by C++23 (#7990) 2026-08-10 17:05:18 +00:00
Ayaz Salikhov
a24caaa6ea docs: Rearrange & simplify build/nix/environment docs (#7985) 2026-08-10 15:00:30 +00:00
Ayaz Salikhov
07b9c59b89 build: Remove protobuf dependencies from Nix (#7984) 2026-08-10 13:08:15 +00:00
Sergey Kuznetsov
63d8772f69 chore: Remove corrosion from nix (#7982) 2026-08-10 11:58:49 +00:00
Gregory Tsipenyuk
94bccb3a5a fix: Fix MPT/DEX Audit/Attackathon reports (Phase 2) (#7537)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Sergey Kuznetsov <skuznetsov@ripple.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
Co-authored-by: Andrzej Budzanowski <andrzej.budzanowski@neti-soft.com>
Co-authored-by: Marek Foss <marek.foss@neti-soft.com>
Co-authored-by: Alex Kremer <akremer@ripple.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
Co-authored-by: Bart <bthomee@users.noreply.github.com>
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-07 21:53:54 +00:00
Mayukha Vadari
0fb92c3194 refactor: Use SeqProxy instead of uint32 for all sequence-based keylets (#7890)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-08-07 21:29:11 +00:00