fix(accounts): declare unpriceable positions instead of dropping them silently - #9306
Merged
Merged
Conversation
… silently
/api/v1/accounts/{ss58}/positions published position_count 0,
total_stake_alpha 0 and NO degraded block for coldkeys that demonstrably hold
positions in the ledger it reads. Coldkey 5Df7xwEP... is the first row of
chain.nominator_positions (hotkey 5FyVinYp..., netuid 18, share_fraction 1) and
the route answered zero -- indistinguishable from "this account delegates
nothing".
buildAccountPositions prices each position off the live `neurons` table and
skips any it cannot price. That skip is CORRECT and stays: #9066 removed the
unpriced_* escape hatches, so a position with no known stake must not be
published with a fabricated 0 stake_tao. What was missing is any signal that it
happened.
`neurons` carries only currently-registered neurons while the position ledger
is a snapshot, so the overlap is much worse than it looks: sampling eight
distinct hotkeys from the live ledger, only ONE was present in `neurons`.
`neurons` itself is healthy -- 129 netuids, 30,085 rows, captured minutes ago
-- so this is not a stale-tier problem that will heal on its own.
Add POSITIONS_DEGRADED_UNPRICEABLE, set whenever ANY row was dropped for want
of a price. Any, not all: a partially-priced total understates the account just
as silently and is harder to notice, because the payload looks healthy for
carrying positions. A malformed row is deliberately NOT counted -- that is bad
data, not an unpriced holding, and conflating them would report a pricing
problem that is not happening.
The existing annotator's snapshot_predates_stake_activity still wins when both
apply. Both mean "do not trust this total"; that one says why more usefully and
carries the two provenance stamps this pure builder cannot see. When the
annotator does not contradict the zero, this marker survives its pass.
No schema change: degraded.reason is a free-form string, and the block is
absent on every trustworthy answer, so a consumer that ignores it reads exactly
what it read before.
Four mutations are each caught: reverting to the silent skip, marking only
all-zero results, marking when nothing was dropped, and counting malformed rows
as unpriceable.
Refs #9305
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
metagraphed-data-api | 3ace7f3 | Aug 03 2026, 08:40 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
metagraphed-registry-sync-api | 3ace7f3 | Aug 03 2026, 08:40 PM |
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
/api/v1/accounts/{ss58}/positionspublishedposition_count: 0,total_stake_alpha: 0and nodegradedblock for coldkeys that demonstrably hold positions inthe ledger it reads.
Coldkey
5Df7xwEP…is the first row ofchain.nominator_positions— hotkey5FyVinYp…,netuid 18,
share_fraction: 1— and the route answered zero. Indistinguishable from "this accountdelegates nothing."
The skip is correct; the silence was not
buildAccountPositionsprices each position off the liveneuronstable and skips any it cannotprice. That skip stays: #9066 removed the
unpriced_*escape hatches, so a position with noknown stake must not be published with a fabricated
stake_tao: 0. What was missing is any signalthat it happened.
neuronscarries only currently-registered neurons while the position ledger is a snapshot, sothe overlap is far worse than it looks — sampling eight distinct hotkeys from the live ledger, only
1 of 8 was present in
neurons. Andneuronsitself is healthy (129 netuids, 30,085 rows,captured minutes ago), so this does not heal on its own.
Fix
POSITIONS_DEGRADED_UNPRICEABLE, set whenever any row was dropped for want of a price.Any, not all: a partially-priced total understates the account just as silently and is harder
to notice, because the payload looks healthy for carrying positions.
A malformed row is deliberately not counted — that is bad data, not an unpriced holding, and
conflating them would report a pricing problem that is not happening.
The existing
snapshot_predates_stake_activitystill wins when both apply: both mean "do not trustthis total", and that one says why more usefully and carries the two provenance stamps this pure
builder cannot see. When the annotator does not contradict the zero, this marker survives its pass
— both directions are tested.
No schema change.
degraded.reasonis a free-form string, and the block is absent on everytrustworthy answer, so a consumer that ignores it reads exactly what it read before.
validate:contract-driftpasses andnpm run buildproduces no diff.Verification
Four mutations, each caught:
Patch coverage 9/9 = 100% with every branch taken, measured by intersecting the diff's line
ranges with v8's uncovered set. 457 tests pass across the positions suites plus
request-handlers-entities;tsc --noEmit, prettier and eslint clean.Two things this does NOT fix, both filed in #9305
nominator_positionsdoes not exist as a table in production D1 (onlyaccount_position_dailydoes), so fix(accounts): nominator_positions has no live refresh — /positions is frozen at the export and drifts forever #9273's hot leg can never fire and the route is pinned to a frozen 2026-08-02 export.
Whether to create it or abandon the lane is a call I did not make unilaterally — it is a
production schema change.
dropped_position_countis a contract question leftopen there.
Refs #9305