feat: emission scoring — capacity, volume, credibility ramp#324
Merged
Conversation
Two new multipliers on top of crown_share × success_rate³: - capacity_factor = min(1.0, collateral / max_swap_amount). A miner at the collateral floor (0.1 of 0.5 max) only covers 20% of the swap-size band; emission scales accordingly. Cold-start fail-safe to 1.0. - volume_factor = (1 - α) + α · min(1.0, vol_share / crown_share), α=0.5. Idle crown holders lose half their reward. Over-serving is capped at 1.0, so wash swaps never amplify reward — the anti-self-route guarantee. Both shortfalls recycle to RECYCLE_UID; nothing transfers to other miners. Aggregation windows: capacity is a snapshot at scoring time, volume is the 600-block scoring window. Same window as crown_share — apples-to-apples. Wiring: - swap_outcomes.tao_amount column (idempotent ALTER). - event_watcher passes SwapCompleted tao_amount through to state_store. - new state_store.get_volume_since aggregator. - scoring trace surfaces cap, vol_share, vol_factor per miner.
Zero-outcome miners earn 0 instead of the legacy 1.0 optimistic default — the free-emission hole that let fresh hotkeys farm crown without serving swaps. Credibility climbs linearly with closed swaps and tops out at CREDIBILITY_RAMP_OBSERVATIONS (10), so a real new miner crosses the ramp in 10 fulfillments and is then indistinguishable from a long-running one. success_rate = raw_rate × min(1.0, total / CREDIBILITY_RAMP_OBSERVATIONS) Per-miner scoring trace surfaces the ramp position so operators can see exactly how many more swaps they need: uid=12 hotkey=5C1a.. crown_blk=400 sr=0.500 (5/10 closed, ramp=0.50) ... Tests get a baseline_credibility=True kwarg on the make_validator fixture (default True) so every test that isn't specifically exercising the ramp gets a fully-credible miner for free. New TestCredibilityRamp class exercises the ramp directly with baseline_credibility=False.
…ancy - Inline bounds_cache + collateral reads into calculate_miner_rewards; drop the read_max_swap_amount / read_miner_collateral helpers (they were scoring-internal one-shot wrappers, not first-class concepts). - Move WeightingTrace to scoring_trace.py where it belongs and add record_capacity / record_volume / record_credibility methods so scoring.py never touches trace fields directly. - Push the idle-network short-circuit into volume_factor so the callsite has one branch instead of two; signature now takes (vol_rao, total_volume_rao, crown_share) — pure-math, no callsite duplication. - closed_swaps + credibility_ramp live on WeightingTrace, so log_scoring_trace no longer needs success_stats threaded through. - Refresh the module docstring to the actual reward equation.
- Direct unit tests for WeightingTrace.record_capacity / record_volume / record_credibility (math + edge cases: idle network, over-serve cap, ramp_target=0). - max_swap_amount RPC failure → capacity_factor fail-safes to 1.0 (previously only the collateral failure path was covered). - Reword diagnose_non_earner reason: 'slashed_credibility_zero' is misleading post-ramp (new miners also land at sr=0) → 'credibility_zero' with a comment.
anderdc
approved these changes
May 13, 2026
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.
Problem → Solution
Problem 1 — Idle crown farming. A miner can post just-above-market rates, self-reserve to lock themselves out, and earn full emission for "being available" without ever serving swaps. We can't simply stop paying reserved miners (inverse exploit: attackers force-reserve real miners to zero their emissions).
→ Capacity + volume weighting on per-direction rewards. Independent multipliers on the existing crown-time × success_rate³ formula:
capacity_factor = min(1.0, collateral / max_swap_amount). A miner at the 0.1 TAO collateral floor with a 0.5 TAO max swap only covers 20% of the swap-size band, so they earn 20% of what they'd otherwise win. Cold-start fail-safe to 1.0 when bounds aren't readable.volume_factor = (1 - α) + α × min(1.0, vol_share / crown_share), α=0.5. Idle crown holders lose half; over-serving is capped at 1.0 — the cap is the anti-wash-trade guarantee (no incentive to force volume past parity with your crown share). Quiet network (zero volume) → factor 1.0, no penalty.Each shortfall recycles to
RECYCLE_UID; nothing transfers between miners.Problem 2 — New-miner free emission.
success_rate(stats)defaulted to 1.0 for zero-outcome miners, so a fresh hotkey holding crown earned the full cubed multiplier (1.0³ = 1.0) before serving anything. Cheap sybil farming surface.→ Linear credibility ramp.
CREDIBILITY_RAMP_OBSERVATIONS = 10. Zero observations → zero credibility. Each closed swap (completed or timed-out) climbs the ramp by 10%. At ≥10 closed swaps the ramp is a no-op and the function returns the raw success rate exactly as before. A miner with 8/10 still gets0.8³ = 0.512— no behavior change at steady state, only the bootstrap window changes.This is the right shape for the network: it incentivizes brand-new miners to actually compete for crown and serve swaps (each successful fulfillment is a measurable credibility gain), instead of dropping in and farming from day one.
Recycling
All four shortfall sources flow to
RECYCLE_UID:sr³< 1)collateral / max_swap< 1)vol_share / crown_share< 1)None redirect to other miners. Pool conservation is invariant (
rewards.sum() == 1.0always).Surfacing
The per-round scoring trace now reads per crown earner:
A miner sees exactly which factor zeroed them: collateral too low → raise; volume too low → fulfill more; closed swaps too few → keep grinding.
Test plan
ruff check allways/ tests/— cleanruff format --check— all formattedpytest tests/— 461/461 passing locally (97 in scoring, including 19 new for capacity/volume/credibility behavior)rewards.sum() == 1.0across mixed-factor scenariosduplicate columncaught)bounds_cache.max_swap_amount()returns 0 → capacity_factor = 1.0 (no zeroing on first pass post-restart)aw-vali, watch the scoring trace surface the new factors on the next pass