Collateral-floor sweep: drive vote_deactivate after a min_collateral raise - #616
Open
anderdc wants to merge 2 commits into
Open
Collateral-floor sweep: drive vote_deactivate after a min_collateral raise#616anderdc wants to merge 2 commits into
anderdc wants to merge 2 commits into
Conversation
set_min_collateral only rewrites the Config: an active miner left under the new floor can no longer be reserved (open_or_request gate), so the fee/slash paths that auto-deactivate under-floor miners never fire and they stay active forever — unreservable by takers yet still earning crown. The contract's vote_deactivate quorum path was built for exactly this, but nothing drove it. CollateralFloorSweep watches the TTL-cached Config each forward step — steady state is a single integer comparison, no RPC. Only when the cached floor rises (or once on boot, covering a raise made while the validator was offline) does it scan MinerState accounts and vote to deactivate active idle miners below the floor. Busy miners and votes short of quorum stay pending and are rechecked with per-miner reads on a slow cadence until the set drains. Watch mode logs WOULD-votes. Deploy this to validators before raising min_collateral. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The scan obligation is now an armed flag set on boot and on each raise edge, cleared only by a completed scan: an RPC failure during the arm scan retries on the slow cadence instead of silently dropping the one event the sweep exists for. The retry cursor advances before RPC calls so failures back off rather than re-firing every forward step, a fresh raise resets the cursor so it never waits out a stale back-off, and one unreadable pending miner skips instead of aborting the recheck pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
anderdc
force-pushed
the
feat/floor-sweep
branch
from
August 4, 2026 14:35
34e20e7 to
fa21dda
Compare
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.
What
Stacked on #614. The precondition for raising
min_collateral(planned 0.5–1 SOL): without this, an admin floor raise strands every under-floor miner as a permanent zombie —set_min_collateraldoesn't touchMinerState,open_or_requestrefuses to reserve them, so the fee/slashapply_penaltypath that auto-deactivates under-floor miners can never fire. They stayactiveindefinitely, unreservable by takers but still earning crown. The contract'svote_deactivatequorum instruction handles exactly this case; it just had no driver — no discriminator inlayouts.py, no client method, no validator loop.Cost model (the "be smart about RPC" part)
min_collateraloff the existingSolanaConfigCache(one Config read per 300s already paid for by swap bounds) and compares it to the last-seen value — one int comparison per forward step.getProgramAccountsscan runs only when the cached floor rises, or once on the first step after boot (covers a raise that happened while this validator was offline). Floor decreases don't rescan.busy_until— they retry once idle; they can't take new swaps, so everyone converges within one fulfillment timeout), and cast votes awaiting quorum (a miner leaves pending only when the chain showsactive = falseor collateral ≥ floor, so a restart or reset round can't strand a half-kicked miner).has_votedgate prevents duplicate votes; a miner that tops up instead of getting kicked is released.Pieces
layouts.py:vote_deactivatediscriminator (sha256(global:vote_deactivate)[:8], derivation verified against the knowndeactivatediscriminator).client.py:vote_deactivate(miner)mirroringvote_activatewithREQ_DEACTIVATE.bounds_cache.py:min_collateral()accessor on the existing TTL cache.floor_sweep.py: the sweep + a never-raisesmaybe_sweep_floorforward hook (same containment pattern asmaybe_vote_weights). Respects watch mode (WOULD vote_deactivate …).Deploy order
Validators must run this before the
alw admin set-min-collateralraise fires — sequence: merge → deploy to ≥67% of validator stake → raise the floor. The under-floor zombie window is then bounded by vote latency (minutes), not forever.Full suite: 856 passed.
🤖 Generated with Claude Code