Skip to content

Fix collateral-baseline zeroing that recycled emissions (v1.0.9)#443

Merged
LandynDev merged 3 commits into
testfrom
fix/collateral-baseline-zeroing
Jun 1, 2026
Merged

Fix collateral-baseline zeroing that recycled emissions (v1.0.9)#443
LandynDev merged 3 commits into
testfrom
fix/collateral-baseline-zeroing

Conversation

@anderdc
Copy link
Copy Markdown
Collaborator

@anderdc anderdc commented Jun 1, 2026

Problem

Subnet 7 emissions were recycling ~90–100% to RECYCLE_UID. Root cause confirmed on the live validator (sqlite + logs): the in-process event watcher records a miner's collateral as 0 whenever it applies a fee/slash delta with no prior baseline.

On SwapCompleted/SwapTimedOut, _apply_collateral_delta(block, miner, -fee/-slash) computes prior + delta, where _latest_collateral returned 0 for a miner with no collateral event, and _record_collateral_event clips negatives to 0. Smoking gun: UID 65's only collateral row was 0 @ block 8311293 — the exact block of its SwapCompleted #2073 — while axon-preflight logs showed ~0.48 TAO on-chain throughout.

The #409/#423 capacity / can_fund crown gate then reads that 0 (capacity_factor → 0, can_fund → false) and drops the miner from crown entirely → 0 emission. 8 of 14 tracked miners had latest collateral = 0, so crown only reached the few correctly-tracked miners and otherwise recycled. Scoring traces: window=[8312102,8312402] recycled=1.0, [8312402,8312702] recycled=0.90.

Secondary: the scoring-trace diagnostic mislabeled these miners as outbid (diagnose_non_earner never checked collateral and wasn't direction-aware — it printed UID 65's better lower-wins rate 279.3 vs best=280 as a loss).

Changes

  • Source fix (event_watcher.py): _apply_collateral_delta skips when there is no known baseline (_latest_collateral now returns Optional[int], None = unknown) instead of fabricating a 0.
  • Reconcile (event_watcher.py, wired in forward.py + neurons/validator.py): reconcile_collateral_from_contract resyncs active miners' collateral to on-chain truth at startup (heals the warm-restart path, which does no contract reads → fixes the corrupted rows on redeploy) and once per scoring round (corrects drift). Writes at the current block only, preserving the #409 per-block / no-post-window-top-up property.
  • Fail-open gate (scoring.py): can_fund and capacity distinguish absent collateral (unknown → fail open: pass / capacity 1.0) from present-0 (genuinely withdrawn → excluded), in both the replay and the live snapshot. Safe because the contract auto-deactivates anyone below min_collateral, so an active miner always holds ≥0.1 TAO.
  • Truthful diagnostics (scoring_trace.py): diagnose_non_earner is direction-aware (tao→btc lower-wins) and collateral-aware → reports insufficient_collateral / unknown_collateral / competitive_but_unfilled instead of a blanket outbid.
  • Version: 1.0.8 → 1.0.9.

Tests

pytest tests/ — 657 passed. New/updated coverage:

  • no-baseline fee must not fabricate a 0 row;
  • reconcile heal / skip-inactive / idempotent / RPC-failure;
  • fail-open on unknown collateral vs exclusion on known-zero (existing test_zero_collateral_zeros_reward updated to seed a real present-0 event, since the old collaterals={'hk_a':0} setup produced an absent series that now correctly fails open);
  • the four diagnose_non_earner reasons.

Verification after deploy

On the validator: confirm the 8 zeroed hotkeys (incl. 5Hb1Lxqm…) repopulate in state.db, V1 scoring recycled drops below 0.90, active best-rate miners' per-uid line shows cap>0/nonzero reward, and leaderboard crownShare rises over subsequent rounds.

anderdc and others added 3 commits June 1, 2026 12:36
The in-process event watcher recorded a miner's collateral as 0 whenever it
applied a fee/slash delta with no prior baseline: _apply_collateral_delta did
prior + delta where _latest_collateral returned 0 for an unseen miner, and the
result clipped to 0. The #409/#423 capacity / can_fund crown gate then read
that 0 and dropped the miner from crown entirely, so honest active best-rate
miners earned nothing and the pool recycled.

- _apply_collateral_delta: skip when there is no known baseline (return None
  from _latest_collateral) instead of fabricating a 0.
- reconcile_collateral_from_contract: resync active miners' collateral to
  on-chain truth; run at startup (heals the warm-restart path) and once per
  scoring round (corrects drift). Writes at the current block only, preserving
  the per-block capacity property.
- scoring gate: fail open on unknown collateral (absent != zero) in both the
  replay and the live snapshot — the contract auto-deactivates anyone below
  min_collateral, so an active miner always holds enough.
- diagnose_non_earner: direction-aware (tao->btc lower-wins) and collateral-
  aware, so it reports insufficient_collateral / unknown_collateral instead of
  mislabeling a better-rate miner as 'outbid'.

Tests cover the no-baseline regression, reconcile (heal/skip/idempotent/rpc),
fail-open vs known-zero, and the diagnosis reasons.
@LandynDev LandynDev merged commit c26adf0 into test Jun 1, 2026
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.

2 participants