Evaluate miner active state per-block in scoring replay#56
Merged
Conversation
Scoring previously filtered candidates by event_watcher.active_miners — the current (scoring-time) active set — so a miner who was active + top-rate throughout the window but deactivated before scoring got zero credit. Active state is now replayed per-block alongside collateral, rate, and busy, via a new ActiveEvent log in ContractEventWatcher. A miner's status at scoring time is irrelevant beyond metagraph membership. - ActiveEvent log + get_active_miners_at / get_active_events_in_range - initialize() seeds an anchor event at cursor for bootstrap-active miners - prune keeps the latest active event per hotkey as a reconstruction anchor - scoring: eligible_hotkeys → rewardable_hotkeys (= metagraph); active evaluated via replay. EventKind.ACTIVE orders first at shared blocks - 17 new tests covering mid-window transitions, deactivated-at-scoring-time, pre-window reconstruction, same-block event ordering, and watcher helpers
Same class of bug as the prior active-flag fix: scoring used the watcher's current scalar for min_collateral, ignored max_collateral entirely, and had no concept of a halted contract. An admin config change retroactively applied to every historical block in the window. - ConfigEvent log + config_current / config_initial mirrors for min_collateral, max_collateral, halted - ConfigUpdated handler now routes through record_config_transition for all tracked keys (previously only min_collateral) - initialize() reads max_collateral + halted from the contract and seeds anchor ConfigEvents at cursor - prune keeps the latest config event per key as a reconstruction anchor - scoring: EventKind.CONFIG ordered first; replay mutates min/max/halted; crown_holders_at_instant returns [] when halted; enforces min_collateral <= collateral <= max_collateral (max=0 disables cap, matching contract semantics) - drop min_collateral kwarg from replay_crown_time_window — replay reconstructs it from config history - 16 new tests: mid-window min raised/lowered, max over-cap exclusion, max lowered mid-window, max=0 no cap, halt mid-window recycles that interval, halt entire window recycles full pool, same-block CONFIG transitions, unknown-key ignored, duplicate no-op, fallback semantics
anderdc
approved these changes
Apr 16, 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.
Summary
event_watcher.active_miners(scoring-time snapshot), so a miner who was active + top-rate throughout the window but deactivated before scoring got zero credit. Active state is now replayed per-block alongside collateral/rate/busy. Only metagraph membership is evaluated at scoring time.ActiveEventlog inContractEventWatcherwithget_active_miners_at/get_active_events_in_rangehelpers.initialize()seeds an anchor event at the cursor so pre-cursor state mirrors the collateral bootstrap pattern. Prune retains the latest event per hotkey as a reconstruction anchor.eligible_hotkeys→rewardable_hotkeys(= current metagraph).EventKind.ACTIVEorders first at shared blocks — active is the tell-all, applies before busy/collateral/rate transitions.Test plan
pytest tests/test_scoring_v1.py— 46/46 passpytest tests/— 289/289 passruff format .+ruff check .— cleancd alw-utils/dev-environment && ./down.sh --force && ./up.sh --chains btc && ./tests/run.sh --chains btc --suite 02set_active(false)between window_end and next scoring tick — confirm they still receive the reward cycle they were active for