Replay collateral per block so capacity reflects window state (#409)#423
Merged
Conversation
Capacity weighting used to multiply already-replayed crown by the miner's *current* collateral, read once at scoring time. A miner could hold crown through the window with low collateral and top up before the next scoring pass to receive full capacity credit retroactively. Mirror the active/busy/rate treatment: emit a per-block CollateralEvent series from CollateralPosted, CollateralWithdrawn, and the silent fee/ slash deductions hidden inside SwapCompleted/SwapTimedOut. The crown replay walks the series alongside everything else and accumulates a cap- weighted-blocks counter that the scoring loop divides back into a time- weighted capacity factor — no contract reads at scoring time. Cold bootstrap anchors current collateral at cursor (same approximation already used for the active set). Warm restart hydrates collateral_events from state.db. Prune preserves the latest row per hotkey as a reconstruction anchor, same rule as active_events.
Resolve conflicts in scoring.py and test_scoring_v1.py:
- reconstruct_window_start_state: keep collateral 5-tuple signature, fold
in test's batched-rate-read docstring, drop stale rates={} init.
- test imports: combine CollateralEvent with new scoring-window constants.
- keep both added test classes (TestHistoricalCollateralReplay +
TestScoringCadenceAndWindow).
- recalibrate collateral replay tests to SCORING_WINDOW_BLOCKS=300
(window now (9_700, 10_000]; mid-window top-up moved to 9_850).
LandynDev
approved these changes
May 31, 2026
anderdc
pushed a commit
that referenced
this pull request
May 31, 2026
…shot snapshot_current_crown_holders unpacked reconstruct_window_start_state into 4 names, but #423 made it return 5 (added collaterals). On a fresh process the forced first scoring pass hit this and threw 'too many values to unpack (expected 4)' every forward step, blocking weight-setting. Unpack collaterals and feed the same can_fund boundary-squat gate the ledger path uses, so the live crown table no longer credits a holder whose collateral can't fund their own smallest legal leg. Adds the first tests for this function (crash regression + squat exclusion).
LandynDev
pushed a commit
that referenced
this pull request
May 31, 2026
* ci(docker): tag pushed image with git sha as well as latest Mirrors gittensor's docker-publish so every main build is pullable by its exact commit sha (entrius/allways:<sha>), not just :latest. Makes pinning/rolling back to a known-good build a direct image pull instead of a source rebuild. * fix(scoring): unpack collateral + apply squat gate in live crown snapshot snapshot_current_crown_holders unpacked reconstruct_window_start_state into 4 names, but #423 made it return 5 (added collaterals). On a fresh process the forced first scoring pass hit this and threw 'too many values to unpack (expected 4)' every forward step, blocking weight-setting. Unpack collaterals and feed the same can_fund boundary-squat gate the ledger path uses, so the live crown table no longer credits a holder whose collateral can't fund their own smallest legal leg. Adds the first tests for this function (crash regression + squat exclusion). * fix(validator): construct bounds_cache before bootstrap_miner_rates #437 moved bounds_cache creation after the bootstrap call, so bootstrap_miner_rates read a not-yet-set attribute and logged 'no attribute bounds_cache', falling back to unbounded (min/max=0) commitment reads on cold start. Move the axon/bounds_cache block above the bootstrap call so the bounds are available. * style: auto-fix pre-commit hooks * chore: bump version 1.0.7 -> 1.0.8 * style: auto-fix pre-commit hooks --------- Co-authored-by: anderdc <me@alexanderdc.com>
LandynDev
pushed a commit
that referenced
this pull request
Jun 1, 2026
* Fix collateral-baseline zeroing that recycled emissions 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. * chore: bump version 1.0.8 -> 1.0.9 * style: auto-fix pre-commit hooks --------- Co-authored-by: anderdc <me@alexanderdc.com>
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.
Summary
Closes #409. Capacity weighting used to multiply already-replayed crown by the miner's current collateral, read once at scoring time via
contract_client.get_miner_collateral. The replayed crown intervals are historical; the collateral read is live. A miner can hold crown through the window with low collateral, then top up before the next scoring pass and receive the higher capacity multiplier retroactively.This PR treats collateral the same way
active,busy,rate, andreservation_pinare already treated: a per-block event series replayed insidereplay_crown_time_window, with the capacity multiplier evaluated at the block being credited.Approach
CollateralEventinevent_watcher.py, with its own in-memory list +_by_hotkeyindex, schema (collateral_events), CRUD, and prune helpers — same shape asActiveEvent/BusyEvent.apply_eventhandlesCollateralPostedandCollateralWithdrawn(writingtotal/remainingrespectively, which the contract emits as post-event balances).SwapCompletedandSwapTimedOutalso tap the series. The contract'sapply_collateral_penaltysilently deducts the fee/slash without emittingCollateralWithdrawn, so the watcher mirrors the deduction fromSwapCompleted.fee_amount/SwapTimedOut.slash_amount.CollateralSlashedis deliberately skipped to avoid double-counting withSwapTimedOut.cursorfor every active miner. Same trade-offactive_eventsalready makes — a fresh validator can only read current state and treats it as constant across the first window.replay_crown_time_windowreconstructs collateral atwindow_startand applies a newEventKind.COLLATERAL. Insidecredit_interval, each holder'ssplitis multiplied bycapacity_factor(collateral_at_block, max_swap_rao)and accumulated into a parallelcap_weighted_blocksdict that the trace exposes.calculate_miner_rewardsdividescap_weighted_blocks[hk] / crown_blocks[hk]to get the time-weighted effective capacity multiplier, then drops the contract read entirely.Notes
MinerStillActiveon chain, so during any crown-earning interval the series is monotone non-decreasing (post-only). The retroactive boost path is closed.WeightingTrace.collateraldropped; the log line now shows onlycap=since the effective multiplier is time-averaged and no single collateral value is the right thing to print.Merged with
testRebased onto latest
testand resolved conflicts inscoring.pyandtests/test_scoring_v1.py:reconstruct_window_start_statekeeps the collateral-aware 5-tuple signature and folds intest's batched-rate-read docstring.TestHistoricalCollateralReplay+TestScoringCadenceAndWindow) are retained.testloweredSCORING_WINDOW_BLOCKS600 → 300, so the window is now(9_700, 10_000].test_mid_window_topup_blends_capacity's top-up moved 9_700 → 9_850 (new midpoint) to keep the 150/150 split; stale window-math comments updated. No behavior change to the fix itself.Test plan
uv run pytest tests/test_scoring_v1.py— 131 passuv run pytest— 624 passuv run ruff check— cleantest_409_retroactive_topup_does_not_boost_windowmirrors the proof in the issuetest_mid_window_topup_blends_capacitycovers the time-weighted average pathtest_swap_completed_deducts_fee_from_collateral_series/test_swap_timed_out_deducts_slash_from_collateral_seriescover the silent deductionstest_prune_keeps_latest_collateral_event_per_hotkeycovers the reconstruction anchor invarianttest_collateral_events_persist_across_hydratecovers warm restart round-trip