Skip to content

Improve realtime correctness, cache isolation, and rendering performance - #2124

Open
antonbelous0v wants to merge 24 commits into
dydxprotocol:mainfrom
antonbelous0v:anton/performance-correctness-audit
Open

antonbelous0v wants to merge 24 commits into
dydxprotocol:mainfrom
antonbelous0v:anton/performance-correctness-audit

Conversation

@antonbelous0v

Copy link
Copy Markdown

d3d402c7 Report the actual indexer height

useApiState returned the validator height for both validatorHeight and indexerHeight.

The hook now reads the real indexer height, so API status, lag reporting, and related analytics no longer compare the validator against itself.

25495dda Share locale state across output components

Output components subscribed to Redux independently to read the selected locale, even though they were already under LocaleProvider.

The selected locale now comes from the existing locale context. This removes a Redux subscription from every Output instance and keeps locale formatting state behind one provider boundary.

9c26bbbf Avoid copying existing BigNumber values

MustBigNumber constructed a new BigNumber even when the input was already a BigNumber.

It now returns existing instances directly. BigNumber operations are immutable, so the copy provided no isolation and only added allocations across a helper with a large number of call sites.

1bcca3d7 Reuse parsed values during number formatting

formatNumberOutput parsed the same input up to four times to calculate its absolute value and sign.

The value is now converted once and reused for all formatting decisions. Tests cover positive, negative, fiat, regular, and compact output.

200eff74 Handle unavailable staking validators

Default-validator selection could call reduce() on an empty array when the configured whitelist had no matches or the validator response was empty.

The selection is now safe for empty responses, falls back to available bonded validators, and excludes jailed or unbonded validators from the default candidates.

6872dd13 Make validator ranking deterministic

Validator ranking used two consecutive sorts with comparators that returned -1 for equal values. This violated the Array.sort() comparator contract and could produce unstable ordering.

Ranking now uses one deterministic comparator:

  1. Lower commission first.
  2. Higher stake weight as the tie-breaker.
  3. 0 for genuinely equal values.

c2022d52 Stop idle chart animation frames

TimeSeriesChart kept a permanent requestAnimationFrame loop alive after zoom animation had completed, and also while no animation was active.

The animation hook now:

  • Schedules frames only while enabled.
  • Uses the latest callback without restarting the loop unnecessarily.
  • Stops after the chart reaches its target zoom.
  • Cancels the pending frame during cleanup.

For an idle chart, this changes the steady-state animation workload from roughly 60 callbacks per second to zero.

2d18368d Avoid redundant orderbook redraws

useDrawOrderbook rebuilt priceSizeMap during every render. Because that new object was an effect dependency, any parent render caused a complete canvas clear, row redraw, and replacement timeout even when orderbook data had not changed.

The map is now stable for the lifetime of the input data, preventing unrelated renders from triggering full orderbook redraws.

7fe11635 Bridge candle history to live updates

The first WebSocket candle snapshot was treated only as an internal baseline and never sent to TradingView.

A candle arriving between the REST history response and WebSocket subscription could therefore be missed until another update arrived.

The initial WebSocket snapshot now publishes its most recent candle. Later messages continue publishing updates to the current candle and any newer candles.

db6132d6 Uncross orderbook levels in linear time

Orderbook uncrossing repeatedly called Array.shift() while removing stale crossed levels. Since every shift moves the remaining array, a large crossed section could degrade to O(n²).

The implementation now advances bid and ask indices and performs one final slice() per side.

Complexity:

  • Before: worst-case O(n²)
  • After: O(n)

9d838708 Find visible chart points with binary search

During zoom animation, TimeSeriesChart scanned the complete historical dataset on every frame to find visible points. It then created another temporary array to calculate the Y range.

Since the chart already relies on chronologically sorted data, visible boundaries are now found with binary search. The Y range is calculated directly without the intermediate map() allocation.

Complexity for selecting visible data:

  • Before: O(n)
  • After: O(log n + k), where k is the number of visible points

This matters most for long histories and animated zooming, where the calculation runs once per frame.

10bc9fda Throttle orderbook scroll measurements

The orderbook middle-row listener called getBoundingClientRect() for every native scroll event. Browsers can emit multiple scroll events per rendered frame, causing repeated synchronous layout reads.

Measurements are now limited to one requestAnimationFrame callback per frame. The listener is passive, and cleanup removes it from the exact DOM node where it was registered.

This also fixes a listener leak caused by reading a changed ref.current during cleanup.

57bd9d6d Keep chart gradient IDs stable and isolated

Threshold charts generated a new random SVG ID on every render, forcing unnecessary threshold DOM updates.

Gradient IDs were also shared globally between chart instances, so multiple charts could reference another chart's gradient definitions.

IDs are now:

  • Stable across renders.
  • Unique per chart instance.
  • Unique per series.
  • Shared consistently between threshold fills and their gradient definitions.

9b1cd7b8 Tear down replaced candle subscriptions

Registering a TradingView candle subscription with an existing listenerGuid overwrote the stored teardown callback without invoking it.

The old WebSocket subscriber remained active and could continue delivering duplicate candles.

Subscription replacement is now atomic: the previous subscription is torn down before the new one is registered. Invalid replacement subscriptions also remove the old listener.

2510eb21 Reuse compact number formatters

Every compact-number and compact-fiat render created a new Intl.NumberFormat instance.

ICU formatter construction is substantially more expensive than calling format() on an existing formatter. Formatters are now cached by locale and currency mode.

The cache is bounded by the supported locale set and the two compact formatting modes.

c347a24a Revalidate markets when network data changes

Market validation was memoized using only marketIds.length.

If two networks exposed the same number of markets, switching networks did not invalidate the memoized market ID. The application could keep a market or prediction-market classification from the previous network.

Validation now depends on the actual market collections, and route validation runs when the available market IDs change.

21d76562 Bound WebSocket gap recovery work

MissingMessageDetector created one timeout for every missing message_id.

An anomalous jump in IDs could synchronously allocate hundreds of thousands or millions of timers, freezing the tab and causing a large memory spike.

Normal small gaps are still tracked individually. Gaps above the safe bound now trigger the existing reconnect recovery path immediately instead of allocating an unbounded timer queue.

af423f19 Isolate withdrawal limits by network

Withdrawal capacity and withdrawal/transfer gating status used global React Query keys.

After switching networks, the UI could reuse limits and gating state from the previous network until the cache became stale.

The keys now include:

  • Selected network.
  • USDC denomination where relevant.

Capacity calculation also reacts correctly when the withdrawal safety feature changes at runtime.

50379fac Isolate vault queries by network

Vault queries reused global cache entries across networks.

The following data is now scoped to the selected network:

  • Vault summary and history.
  • Subvault histories.
  • Vault positions.
  • User vault account state.
  • Withdrawal slippage calculations.

This prevents mainnet and testnet TVL, balances, positions, and slippage from being mixed.

76317962 Scope transfer fee simulations to the account

Transfer fee simulations were cached only by amount, recipient, and transfer type.

The same inputs after an account or network switch could reuse a fee calculated with a different wallet or chain context.

The query key now includes both the selected network and dYdX address.

4013f37a Scope asset metadata cache to its endpoint

Asset metadata and prices used the same cache key after the metadata endpoint changed.

A network switch could therefore continue showing metadata and prices from the previous environment for up to five minutes.

The metadata endpoint is now part of the cache key.

dadfa9b1 Scope deposit addresses to the indexer

Bridging deposit addresses were cached only by dYdX address.

The same account on another network could reuse addresses returned by the previous indexer endpoint.

The active indexer URL is now part of the query key.

c71f39be Scope Turnkey sessions to the wallet

The wallet-export dialog stored the Turnkey session under one global query key.

After switching embedded wallets, a cached session from the previous wallet could continue to satisfy the export UI's session check.

The session query is now keyed by the concrete Turnkey wallet ID and remains disabled until that wallet is available.

88518a2f Isolate affiliate data by indexer

Affiliate queries were not scoped to their indexer, including the leaderboard query with staleTime: Infinity.

After switching networks, the application could indefinitely display affiliate data from the previous environment.

The active indexer endpoint is now included in the keys for:

  • Affiliate metadata.
  • Account affiliate statistics.
  • Maximum earning calculations.
  • Affiliate leaderboard.
  • Referral code resolution.
  • Referred-by state.

@antonbelous0v
antonbelous0v requested a review from a team as a code owner September 23, 2026 12:50
@vercel

vercel Bot commented Sep 23, 2026

Copy link
Copy Markdown

@antonbelous0v is attempting to deploy a commit to the dYdX Trading Team on Vercel.

A member of the Team first needs to authorize it.

This branch has not been deployed

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant