Skip to content

account_objects unconditionally walks NFT pages even when type filter excludes NFTokenPage #3060

Description

@RaymondSeven

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      ✅ Merged

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions