Skip to content

Release v1.5.1 - #412

Merged
pbennett merged 4 commits into
mainfrom
dev
Aug 20, 2026
Merged

pbennett merged 4 commits into
mainfrom
dev

Conversation

@pbennett

Copy link
Copy Markdown
Contributor

Three UI bug fixes, released as v1.5.1. No contract or nodemgr changes.

Every validator's Status column read "Not active"

Nodely serializes the uint64 columns of poolvotingperformance/24hr as bare JSON numbers, but the lookup compared validatorid strictly against a string, so validator.perf was undefined for all 56 validators the feed covers:

(q) => q.validatorid === baseValidator.id.toString()   // 147 === "147"

interfaces/nodely.ts declared those columns as string, which is what kept tsc quiet. The miss then presented as a measurement rather than as missing data, because the branch rendering "Not active" was only reachable via NaN — both NaN >= 0.7 and NaN < 0.7 are false.

The lookup moves to findValidatorPerf, which coerces both sides; the feed has flipped these columns between quoted strings and raw numbers once already. ValidatorStatus now tests for absence directly, so "Nodely has no row for this validator" and "this validator is performing badly" stop sharing a branch, and formats the tooltip through formatAmount — it was rendering 99.87096774193549%.

Against the live feed: the old predicate matched 0 of 56 validators, the new one matches all 56 → 53 green, 3 yellow.

Pool global state the bulk read didn't carry stayed missed

d386587 collapsed per-pool state into one accountInformation read on the registry, which left two gaps:

  • processPoolData fell back on the presence of the entry, not on what it held. A pool whose state came back partial decodes to {} — truthy — so it kept the empty entry and returned no lastPayout, which the Status column renders as "Payouts stopped".
  • algodVer lost its fallback entirely. StakingPoolInfo reads it only from the bulk map, and the per-pool read inside the metrics query keeps lastPayout while discarding the algodVer it fetched. Any pool the map lacked reported its node version as -- for the rest of the session.

Both now gate on isPoolGlobalStateComplete, which tests lastPayout rather than the field being displayed. lastPayout is written when the pool is created (stakingPool.algo.ts sets it to the creation round as the first epoch's baseline), so every pool has one and its absence can only mean an incomplete read. Gating on algodVer would have been wrong: it is only written once the node daemon reports in, and 88 of MainNet's 283 pools legitimately have none — keying on it would re-read a third of all pools on every visit and still show --.

Also

The 1020 test fixture claimed a missing lastPayout stood for "a pool that has never paid out", which the contract does not do. It now models the case that does occur — a pool whose daemon has never reported, so no algodVer.

Testing

ui suite 200 → 206 tests; typecheck, lint, prettier, and build clean.

Both fixes were checked against their own regression: reverting the perf compare fails 4 of the 7 new nodely.spec.ts cases, and reverting the fallback condition fails "recovers a pool whose bulk entry came back without lastPayout" with expected undefined to be 1080n.

Not verified in a browser: the algodVer fallback only fires when algod truncates or the bulk query fails, which can't be reproduced against live MainNet. Its normal path — bulk hit, no extra request — is covered by the specs.

Every validator's Status column rendered a red gauge tooltipped "Not active".

Nodely serializes the uint64 columns of poolvotingperformance/24hr as bare JSON
numbers, but the lookup compared `validatorid` strictly against a string:

    (q) => q.validatorid === baseValidator.id.toString()   // 147 === "147"

so `validator.perf` was undefined for all 56 validators the feed covers. The
interface declared those columns as `string`, which is what hid it from tsc.

The miss then presented as a measurement rather than as missing data, because
the branch that renders "Not active" was only reachable via NaN:

    const perfScore = Number(validator.perf)   // Number(undefined) -> NaN
    if (perfScore >= 0.7) { Normal }           // NaN >= 0.7 -> false
    else if (perfScore < 0.7) { Watch }        // NaN <  0.7 -> false
    else { Error; 'Not active' }               // NaN only

The lookup moves to `findValidatorPerf`, which coerces both sides - the feed has
flipped these columns between quoted strings and raw numbers once already, and a
strict compare turns that into a silent blank for every row. ValidatorStatus now
tests for absence directly, so "Nodely has no row for this validator" and "this
validator is performing badly" stop sharing a branch, and formats the tooltip
through formatAmount (it was rendering 99.87096774193549%).

Two defects in the sibling payout indicator go with it: `!roundsSinceLastPayout`
treated a fresh 0 as "Payouts stopped, behind 0 seconds", and a Number was
compared against bigint literals. Threshold bands and colors are unchanged.
Moving pool state to a single accountInformation read on the registry left two
gaps where a pool the bulk request missed stayed missed.

`processPoolData` fell back on the presence of the entry, not on what it held. A
pool whose state came back partial decodes to `{}`, which is truthy, so it kept
the empty entry and returned no lastPayout - which the Status column renders as
"Payouts stopped".

`algodVer` lost its fallback entirely. It came off LocalPoolInfo, and
StakingPoolInfo now reads it only from the bulk map; the per-pool read inside the
metrics query keeps lastPayout and discards the algodVer it fetched. Any pool the
map lacks reported its node version as "--" for the rest of the session.

Both now gate on `isPoolGlobalStateComplete`. It tests lastPayout rather than the
field being displayed, because lastPayout is written when the pool is created -
stakingPool.algo.ts sets it to the creation round as the first epoch's baseline -
so every pool has one and its absence can only mean an incomplete read. Gating on
algodVer instead would have been wrong: it is only written once the node daemon
reports in, and 88 of MainNet's 283 pools legitimately have none, so keying on it
would re-read a third of all pools on every visit and still show "--".

The 1020 fixture claimed a missing lastPayout stood for "a pool that has never
paid out", which the contract does not do. It now models the case that does
occur - a pool whose daemon has never reported, so no algodVer - and the tests
around it cover the fallback firing on an incomplete entry and staying put on a
complete one.
@pbennett
pbennett requested a review from drichar as a code owner August 20, 2026 18:16
@vercel

vercel Bot commented Aug 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
reti Ready Ready Preview Aug 20, 2026 6:25pm
reti-testnet Ready Ready Preview Aug 20, 2026 6:25pm

Request Review

@pbennett
pbennett merged commit 2935cee into main Aug 20, 2026
3 of 5 checks passed

This branch was successfully deployed

2 active deployments
Production – reti — bdb51c26 Deployed Aug 20, 2026 by vercel[bot]
Preview – reti-testnet — bdb51c26 Deployed Aug 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant