Skip to content

Stop deep-comparing the full transaction history per check in selectLocalTransactions #31499

Description

@MajorLift

Performance audit finding · Severity: High · Effort: Medium · Fix risk: Moderate · Test safety net: Partial
Owner: CODEOWNERS best-effort (transactions)
File: app/selectors/transactionController.ts:247

What is this about?

selectLocalTransactions is a createDeepEqualSelector over a pipeline that allocates aggressively upstream: selectNonReplacedTransactions spreads and sorts the merged transaction + pending-smart-transaction arrays on every recompute (transactionController.ts:240 area), and the result function filters the full list again. Because the wrapper is deep-equal, its inputs are deep-compared on every check — for a power-user transaction history (~2,000 entries) that is an O(n) structural comparison of large transaction objects per consumer per flush, on top of the spread+sort chains re-running on every TransactionController state change.

Why it matters

The transaction list is a primary power-user surface; this pipeline runs its compares a few times per second (250ms batched flush) for as long as the activity view is mounted, with cost proportional to history size. None of the 2026-06-09 audit-run issues cover it (#31354/#31355 cover the adjacent Map/Set allocations only).

Scenario

N/A — see Technical Details.

Design

N/A — internal performance change; no UI/design impact.

Technical Details

Fix direction

  1. Establish the reference-stability contract of the inputs: TransactionController state arrives via the engine slice (per-controller key replace, Immer structural sharing), so the raw transactions array reference changes only when transactions actually change. If so, the deep-equal wrapper is paying for stability the store already provides — narrow the inputs and downgrade toward plain createSelector.
  2. Move the dedupe/sort/filter into one memoized step keyed on the raw transactions input rather than re-running per layer, so the chain recomputes once per actual data change.
  3. If consumers need stability across genuinely-churning inputs, prefer a resultEqualityCheck on the (much smaller) output over deep-comparing the full input arrays.

Threat Modeling Framework

N/A — performance-only change; behavior is preserved, no new data flow / trust boundary / attack surface.

Acceptance Criteria

  • Recompute count ~1 per actual transaction change (not per flush); no full-array deep compares per check on the hot path.
  • Profiler on a power-user profile (~2,000 tx): activity view render time and JS-thread time during balance-poll flushes drop measurably.
  • Reassure perf-test on the transactions view locks the win in CI.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Sev1-highAn issue that may have caused fund loss or access to wallet in the past & may still be ongoingSev2area-performanceIssues relating to slowness of app, cpu usage, and/or blank screens.ta-needs-engineer-escalationTriage-Agent - Applied when confidence is below thresholdta-triagedteam-transactionsTransactions team

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    To be fixed

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions