When a caller passes a type filter (e.g. type=offer) to account_objects, Clio still traverses the entire NFT-page linked list before starting the directory walk. Rippled skips NFT iteration entirely in this case via iterateNFTPages. The result: up to limit (max 400) wasted backend reads per request that return zero matching objects and consume the pagination budget.
Current behavior
AccountObjectsHandler::process calls traverseOwnedNodes(..., nftIncluded=true) unconditionally. Inside traverseOwnedNodes (RPCHelpers.cpp:668), the branch enters traverseNFTObjects based solely on nftIncluded and the marker shape — the caller's typeFilter is never consulted. Each NFT page costs one fetchLedgerObject. The limit is decremented by pages fetched (limit -= nftsCount), so a type=offer limit=400 request on an account with ≥400 NFT pages returns an empty array with a marker, having exhausted the entire budget on irrelevant reads.
Expected behavior
Mirror rippled's guard at AccountObjects.cpp:60-62:
bool iterateNFTPages =
(!typeFilter.has_value() || typeMatchesFilter(typeFilter.value(), ltNFTOKEN_PAGE)) &&
dirIndex == beast::zero;
When the type filter excludes ltNFTOKEN_PAGE, skip the NFT traversal entirely and give the full limit budget to the directory walk.
Impact
- Filtered requests against NFT-heavy accounts are orders of magnitude more expensive on Clio than on rippled for zero useful output.
- Pagination forces
ceil(N_pages / limit) round trips of pure waste before any matching object is returned.
- Public Clio endpoints absorb unnecessary Cassandra/storage load from any client using type filters on NFT-holding accounts.
Suggested fix
Option A — plumb typeFilter into traverseOwnedNodes and gate the NFT branch:
bool const filterAllowsNFT =
!typeFilter.has_value() ||
std::find(typeFilter->begin(), typeFilter->end(), ripple::ltNFTOKEN_PAGE) != typeFilter->end();
if (nftIncluded and filterAllowsNFT and (!jsonCursor or isNftMarkerNonZero)) {
Option B — in the handler, set nftIncluded = false when typeFilter excludes ltNFTOKEN_PAGE.
Affected code
src/rpc/RPCHelpers.cpp — traverseOwnedNodes (8-arg overload), line 668
src/rpc/handlers/AccountObjects.cpp — process, line 92 (hardcoded true)
tests/unit/rpc/handlers/AccountObjectsTests.cpp — TypeFilter test (line 572) mocks the wasted NFT fetch as expected; should assert Times(0) after fix
Rippled reference
src/xrpld/rpc/handlers/account/AccountObjects.cpp:60-62 — the iterateNFTPages short-circuit that Clio is missing.
When a caller passes a
typefilter (e.g.type=offer) toaccount_objects, Clio still traverses the entire NFT-page linked list before starting the directory walk. Rippled skips NFT iteration entirely in this case viaiterateNFTPages. The result: up tolimit(max 400) wasted backend reads per request that return zero matching objects and consume the pagination budget.Current behavior
AccountObjectsHandler::processcallstraverseOwnedNodes(..., nftIncluded=true)unconditionally. InsidetraverseOwnedNodes(RPCHelpers.cpp:668), the branch enterstraverseNFTObjectsbased solely onnftIncludedand the marker shape — the caller'stypeFilteris never consulted. Each NFT page costs onefetchLedgerObject. The limit is decremented by pages fetched (limit -= nftsCount), so atype=offer limit=400request on an account with ≥400 NFT pages returns an empty array with a marker, having exhausted the entire budget on irrelevant reads.Expected behavior
Mirror rippled's guard at
AccountObjects.cpp:60-62:bool iterateNFTPages = (!typeFilter.has_value() || typeMatchesFilter(typeFilter.value(), ltNFTOKEN_PAGE)) && dirIndex == beast::zero;When the type filter excludes
ltNFTOKEN_PAGE, skip the NFT traversal entirely and give the full limit budget to the directory walk.Impact
ceil(N_pages / limit)round trips of pure waste before any matching object is returned.Suggested fix
Option A — plumb
typeFilterintotraverseOwnedNodesand gate the NFT branch:Option B — in the handler, set
nftIncluded = falsewhentypeFilterexcludesltNFTOKEN_PAGE.Affected code
src/rpc/RPCHelpers.cpp—traverseOwnedNodes(8-arg overload), line 668src/rpc/handlers/AccountObjects.cpp—process, line 92 (hardcodedtrue)tests/unit/rpc/handlers/AccountObjectsTests.cpp—TypeFiltertest (line 572) mocks the wasted NFT fetch as expected; should assertTimes(0)after fixRippled reference
src/xrpld/rpc/handlers/account/AccountObjects.cpp:60-62— theiterateNFTPagesshort-circuit that Clio is missing.