Summary
VaultPendingSwapOuts (keeper/query_server.go:301-315) retrieves a single vault's pending swap-outs by scanning the entire PendingSwapOutQueue.IndexedMap and filtering in memory, making the query O(N) in the total number of pending swap-outs across all vaults instead of O(k) in the requested vault's entries. A ByVault secondary index already exists for exactly this access pattern, so the cost grows with global queue size even when the target vault has few or zero pending swap-outs.
Details
The handler uses query.CollectionFilteredPaginate over the full primary map with a per-entry address comparison:
swapOuts, pageRes, err := query.CollectionFilteredPaginate(
ctx,
k.PendingSwapOutQueue.IndexedMap,
req.Pagination,
func(key collections.Triple[int64, uint64, sdk.AccAddress], _ types.PendingSwapOut) (include bool, err error) {
return vault.Address == key.K3().String(), nil // in-memory filter over every entry
},
...
)
The efficient path is already implemented in queue/pending_swap_out.go:
ByVault *indexes.Multi[...] (line 18) — the secondary index keyed by vault address.
WalkByVault (line 171) — IndexedMap.Indexes.ByVault.MatchExact(ctx, vaultAddr), a range scan returning only the requested vault's entries.
The sibling PendingSwapOuts ("list all") endpoint correctly uses query.CollectionPaginate on the full map; only the vault-scoped endpoint uses the wrong traversal.
Constraint: preserve pagination
QueryVaultPendingSwapOutsRequest has a pagination field and the response returns pageRes, so the endpoint's pagination contract must be kept. The straightforward WalkByVault rewrite loads all of a vault's entries into a slice and drops pagination, which would change the API and risk unbounded memory for a vault with many pending swap-outs. The fix should paginate over the ByVault index iterator (e.g. via MatchExact + SDK iterator pagination), not collect everything in memory.
Proposed change
- Rewrite
VaultPendingSwapOuts to traverse the ByVault index (MatchExact / the WalkByVault access pattern) rather than CollectionFilteredPaginate over the full IndexedMap.
- Preserve the existing
pagination request/response behavior by paginating over the index-scoped iterator.
Tasks
Summary
VaultPendingSwapOuts(keeper/query_server.go:301-315) retrieves a single vault's pending swap-outs by scanning the entirePendingSwapOutQueue.IndexedMapand filtering in memory, making the query O(N) in the total number of pending swap-outs across all vaults instead of O(k) in the requested vault's entries. AByVaultsecondary index already exists for exactly this access pattern, so the cost grows with global queue size even when the target vault has few or zero pending swap-outs.Details
The handler uses
query.CollectionFilteredPaginateover the full primary map with a per-entry address comparison:The efficient path is already implemented in
queue/pending_swap_out.go:ByVault *indexes.Multi[...](line 18) — the secondary index keyed by vault address.WalkByVault(line 171) —IndexedMap.Indexes.ByVault.MatchExact(ctx, vaultAddr), a range scan returning only the requested vault's entries.The sibling
PendingSwapOuts("list all") endpoint correctly usesquery.CollectionPaginateon the full map; only the vault-scoped endpoint uses the wrong traversal.Constraint: preserve pagination
QueryVaultPendingSwapOutsRequesthas apaginationfield and the response returnspageRes, so the endpoint's pagination contract must be kept. The straightforwardWalkByVaultrewrite loads all of a vault's entries into a slice and drops pagination, which would change the API and risk unbounded memory for a vault with many pending swap-outs. The fix should paginate over theByVaultindex iterator (e.g. viaMatchExact+ SDK iterator pagination), not collect everything in memory.Proposed change
VaultPendingSwapOutsto traverse theByVaultindex (MatchExact/ theWalkByVaultaccess pattern) rather thanCollectionFilteredPaginateover the fullIndexedMap.paginationrequest/response behavior by paginating over the index-scoped iterator.Tasks
VaultPendingSwapOutsto use theByVaultindex path.