Skip to content

Quality-volume slice and activity-window eligibility (3.4.0) - #727

Merged
anderdc merged 5 commits into
testfrom
feat/im-quality-volume
Sep 10, 2026
Merged

anderdc merged 5 commits into
testfrom
feat/im-quality-volume

Conversation

@anderdc

@anderdc anderdc commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Incentive changes from the 09-08 standup: pay quality volume and replace the warm-up with an activity window. Validator-only; no program change, no allways-db migration.

Reward per miner and lane

eligible × pool × [ (1−β) × crown_share × capacity  +  β × qualified_volume_share ]

1. Quality-volume slice (β = 0.25)

  • A completed fill qualifies iff the miner held the lane's crown at the instant it was reserved, judged at the fill's own size. fill_held_crown runs the same reconstruct + crown_holders_at_instant the live snapshot uses, with the reserved miner always a candidate and can-fund bound to the swap's collateral_amount (a fill the seam routed past a too-thin crown holder still counts). Crown movement after reservation never changes the answer.
  • Anchor is the miner's last RESERVE_START on that hub before SwapCompleted (PoolResolved time); the 30 s pool window is the accepted tolerance. Flag is computed at ingest and stored on clearing_rates (new backing + qualified columns).
  • The β slice is per lane: your qualified hub-leg notional over the lane's, trailing 24 h. Not capacity-scaled (a completed fill was backed by construction), still behind the eligibility gate. A lane with no qualified fills in the window recycles its β.
  • Pools (the α blend) now read qualified volume only. Unqualified fills neither size a pool nor pay.
  • Crown event tables keep 4 h (EVENT_RETENTION_SECS) instead of 1 h so a reservation instant is replayable at completion. Score rows write the qualified share into the existing nullable vol_share column.

2. Activity window replaces the warm-up

  • MIN_SUCCESSFUL_SWAPS is gone. Eligible iff lifetime timeouts ≤ MAX_FAILED_SWAPS (unchanged, on-chain) and at least one completed fill in the trailing ELIGIBILITY_FILL_WINDOW_SECS (12 h), read off the clearing ledger. Any lane, any taker, qualified or not; timed-out and cancelled swaps don't count.
  • Per purse: a lane is live only while its own backing delivered inside the window. is_eligible keeps the any-purse global reading (strikes + trace); direction_eligible adds the lane's purse via purse_active.
  • A validator whose clearing ledger is younger than the window (fresh DB) applies strikes only, so it can't zero the network while catching up (clearing_ledger_since in relay_meta).

3. Capacity curve

Unchanged: the convex ramp (k=2) stays. A linear curve was drafted and reverted on review.

Not in this PR (decided)

  • Paying FULFILLING miners: skipped. A busy purse isn't takeable, so it has no crown to pay; revisit with concurrent swaps per purse.

What to expect on deploy

  • Distributed emission drops by roughly β on the first round (almost every lane has no qualified fill in a 24 h window today); the rest recycles until miners fill their own crowns.
  • Miners will self-fill at min size on the lanes they hold, once per 12 h per purse at least (activity) and once per 24 h (β). Each fill pays the protocol fee + reservation fee to the treasury.
  • One dust fill in a quiet family moves most of that family's pool through α (pre-existing behaviour, now gated on qualified). If that's too jumpy, the fallback is a hub-notional floor on the α term (the Remove MIN_DIRECTION_VOLUME dust floor from scoring #553 dust floor).
  • A validator down > 4 h re-ingesting old completions judges them against pruned rate history; those fills read unqualified (fail-closed, ages out in a day).

Follow-ups

  • Per-pair busy at the next program upgrade (the real BTC fix).
  • compute_direction_pools family share is pair-count-weighted — must become fixed/volume-weighted before [BLOCKED: emissions redesign] Activate alpha-spoke pairs (PR3) #709 activates alpha-spoke pairs.
  • alw miner status has no view of the activity window; the seam could serve last-fill-per-purse.

Tests

2147 passed. New: β slice (TestQualityVolumeSlice), ledger readers, TestFillHeldCrown (band, size-aware thin holder, busy competitor, crown moving after T, end-to-end ingest), activity window (helper, ledger age, edge of window, per purse e2e).

Sister docs PR: entrius/allways-docs-ui (same branch name).

https://claude.ai/code/session_017ZTCEjbjJwe6Te657adCap

Each lane pool now pays (1-beta) on crown time x capacity and beta on the
lane's qualified volume share (QUALITY_VOLUME_BETA = 0.25). A completed fill
qualifies iff the miner held the lane's crown at the instant it was reserved
(its last RESERVE_START on that hub before SwapCompleted), judged at the
fill's own size so a swap the seam routed past a too-thin crown holder still
counts. The flag is computed at ingest by fill_held_crown - the same
reconstruct + crown_holders_at_instant the live snapshot runs - and stored on
clearing_rates (new backing + qualified columns). Pools read qualified volume
only; unqualified fills neither size a pool nor pay. A lane with no qualified
fills in the window recycles its beta slice.

The crown event tables keep four hours (EVENT_RETENTION_SECS) instead of one
so a reservation instant is still replayable at completion. Score rows carry
the qualified share into the existing nullable vol_share column.

CAPACITY_CURVE_EXPONENT goes 2.0 -> 1.0: the depth-weighted band already
splits crown by collateral, so the convex ramp counted depth twice.

Claude-Session: https://claude.ai/code/session_017ZTCEjbjJwe6Te657adCap
Eligibility is now binary on two halves: at most MAX_FAILED_SWAPS lifetime
timeouts (the on-chain counter, unchanged) and at least one completed fill in
the trailing ELIGIBILITY_FILL_WINDOW_SECS (12 h), read off the validator's
clearing ledger. The two-success warm-up is gone: a miner is eligible from its
first completed fill and stays so only by delivering - any lane, any taker,
qualified or not. A lapsed miner drops out of every crown until its next fill.

A validator whose clearing ledger is younger than the window (fresh DB) applies
strikes only, so it cannot zero the network while it catches up; the ledger's
first-write time is stamped in relay_meta.

Claude-Session: https://claude.ai/code/session_017ZTCEjbjJwe6Te657adCap
A lane is live only while its own backing delivered a fill inside the window,
so a miner with a dead SOL watcher and a live TAO purse earns on tao lanes
alone. The clearing ledger read now groups hotkeys by backing; is_eligible
keeps the any-purse global reading (strikes + trace), direction_eligible adds
the lane's own purse via purse_active. Single-purse miners see no change.

Claude-Session: https://claude.ai/code/session_017ZTCEjbjJwe6Te657adCap
Reverts the k=2 -> 1 change: the squared ramp stays, so thin-parked
collateral is penalised harder than the raw ratio and depth is worth posting.

Claude-Session: https://claude.ai/code/session_017ZTCEjbjJwe6Te657adCap
@anderdc anderdc changed the title Quality-volume slice, activity-window eligibility, linear capacity (3.4.0) Quality-volume slice and activity-window eligibility (3.4.0) Sep 9, 2026
@anderdc
anderdc merged commit fae501a into test Sep 10, 2026
3 checks passed
@anderdc
anderdc deleted the feat/im-quality-volume branch September 10, 2026 20:19
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.

1 participant