fix: handle oracle syncing state in explorer#36
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves the explorer’s resilience and UX when the Oracles API is still syncing, by detecting “syncing/no-epochs” states, avoiding hard failures, and surfacing a consistent syncing indicator in key UI cards.
Changes:
- Add shared helpers to detect Oracles syncing / “no epochs found” responses.
- Adjust Oracles API fetching and node availability selection to tolerate syncing/empty ranges and fall back to last-epoch metadata.
- Display a “Syncing oracles” state in Node Performance and PoA rewards, and harden node metadata rendering against missing fields.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
typedefs/blockchain.ts |
Extends Oracles payload typings with sync/error metadata fields. |
lib/oracles.ts |
Introduces shared sync/error detection helpers. |
lib/api/oracles.ts |
Handles “No epochs found…” responses without throwing. |
lib/actions.ts |
Adds fallback from range endpoint to last-epoch endpoint while syncing. |
app/server-components/shared/SyncingOraclesTag.tsx |
Adds a shared syncing tag UI component. |
app/server-components/main-cards/NodePerformanceCard.tsx |
Shows syncing state and guards against empty epoch arrays. |
app/server-components/main-cards/NodeCard.tsx |
Adds safer rendering for potentially missing metadata fields. |
app/server-components/main-cards/LicenseCard.tsx |
Updates node availability callback typing. |
app/server-components/Nodes/NodeListGNDCard.tsx |
Updates node availability response typing usage. |
app/server-components/Licenses/NodeSmallCard.tsx |
Updates node availability response typing usage. |
app/server-components/Licenses/LicenseRewardsPoA.tsx |
Shows syncing state when rewards are not yet computable due to syncing. |
app/server-components/LicensePage/LicensePageNodePerformanceCardWrapper.tsx |
Updates node availability callback typing usage. |
app/server-components/LicensePage/LicensePageNodeCardWrapper.tsx |
Updates node availability callback typing usage. |
app/node/[nodeAddr]/page.tsx |
Updates node availability response typing usage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if ('error' in data.result) { | ||
| if (hasNoEpochsFoundError(data.result.error)) { | ||
| return { | ||
| ...data.result, | ||
| epochs: 'epochs' in data.result && Array.isArray(data.result.epochs) ? data.result.epochs : [], | ||
| epochs_vals: 'epochs_vals' in data.result && Array.isArray(data.result.epochs_vals) ? data.result.epochs_vals : [], | ||
| } as T; | ||
| } |
There was a problem hiding this comment.
The special-case branch for No epochs found... returns data.result cast as T, but data.result can be just { error: string } (no node metadata fields). This makes getNodeLastEpoch/getNodeEpochsRange appear to return a full OraclesAvailabilityResult while potentially omitting required properties, leading to downstream runtime errors. Consider modeling this as a proper union return type (e.g., T | { error: string; epochs: number[]; epochs_vals: number[]; ...syncMeta }) or a dedicated “syncing/empty” result type and updating callers accordingly, rather than as T.
| {epochs | ||
| .slice(-10) | ||
| .reverse() | ||
| .map((epoch, index) => { | ||
| const availability = nodeResponse.epochs_vals.slice(-10)[index]; | ||
| const availability = epochsVals.slice(-10)[index]; | ||
|
|
There was a problem hiding this comment.
In the mobile rendering, epochs are reversed but the availability values are still indexed from the non-reversed epochsVals.slice(-10). This can display mismatched availability percentages for a given epoch. Build a single list of {epoch, availability} pairs (or reverse both arrays consistently) before mapping so epoch/value stay aligned.
| const nodeResponse: types.OraclesAvailabilityResult = await getNodeLastEpoch(nodeAddress); | ||
|
|
||
| ratio1Addr = nodeResponse.node as types.R1Address; |
There was a problem hiding this comment.
getNodeLastEpoch() can now return an empty epochs_vals array for the handled "No epochs found..." case. This file later reads nodeResponse.epochs_vals[0] when building node.recent_history.last_epoch_avail, which will become undefined and lead to NaN% rendering in NodeListNodeCard. Add a guard/fallback (e.g., default to 0 or skip rendering until data exists).
| type OraclesAvailabilityResult = { | ||
| node: string; | ||
| node_alias: string; | ||
| node_eth_address: EthAddress; | ||
| epochs: number[]; | ||
| epochs_vals: number[]; | ||
| error?: string; | ||
| eth_signed_data: EthSignedData; | ||
| eth_signatures: EthAddress[]; | ||
| eth_addresses: EthAddress[]; | ||
| node_is_online: boolean; | ||
| node_is_oracle: boolean; | ||
| node_version: string; | ||
| node_last_seen_sec: number; | ||
| resources: Resources; | ||
| tags?: string[]; | ||
| server_current_epoch?: number; | ||
| server_last_synced_epoch?: number; |
There was a problem hiding this comment.
OraclesAvailabilityResult now includes sync/error metadata (error, server_*_epoch), and the UI/actions code treats some node fields as potentially missing while syncing (e.g., resources, node_last_seen_sec, node_version). The type still marks these as required, which can mask real undefined cases (especially when _doGet returns an error/sync payload). Consider splitting this into a discriminated union (success vs syncing/error) or marking the affected node metadata fields optional to accurately reflect the payload and force callers to handle missing data explicitly.
Summary
/node_epochs_rangesyncing responses (e.g. "No epochs found for the given node") without throwingSyncingOraclesTagaligned with dapp style/node_last_epochfor node metadata when range data is temporarily emptyValidation