Skip to content

fix(engine): shed orphaned expired allocations - #651

Open
blacks1ne wants to merge 1 commit into
QuilibriumNetwork:v2.1.0.25from
blacks1ne:fix/leave-orphaned-expired-epoch-allocations
Open

blacks1ne wants to merge 1 commit into
QuilibriumNetwork:v2.1.0.25from
blacks1ne:fix/leave-orphaned-expired-epoch-allocations

Conversation

@blacks1ne

Copy link
Copy Markdown
Contributor

The orphan leave sweep considered only active allocations, but orphaned allocations become ExpiredEpoch and were therefore unreachable after one epoch.

Include expired allocations in the orphan leave candidates without counting them as active coverage.

Tests: cargo test --locked -p quil-engine --lib

Base: 4eaf1f79f4eb022b9a9d3ccc072064050e5781b6

The orphan-leave sweep was reachable for exactly one epoch per orphan, so
an orphan that survived a boundary became permanently unsheddable.

Being orphaned is what expires an allocation: no worker bound means no
proofs, so it misses its per-epoch re-confirm at the next boundary and
`AllocationBuckets::from_allocations` moves it out of `active` into
`expired_epoch`. The sweep drew its candidates from `active` alone, so
from that moment nothing could propose a Leave for it — not the orphan
path, and not `plan_leaves`, which scores `allocated_descriptors`, itself
filtered by `active`. The node then holds allocations it cannot serve,
indefinitely, with no automatic way out. Observed on a node with 35
allocations against 15 workers: the 20 unbound ones all read
`re-confirm!` and no Leave was ever proposed.

Draw orphan candidates from `active` UNION `expired_epoch`, in a
`orphaned_allocation_filters` helper. `expired_epoch` deliberately does
not join `active` itself — that set is coverage accounting, and an
allocation that has not re-confirmed genuinely does not count — so this
widens only who may be shed. The existing exclusions still apply, on the
expired path as much as the active one: a filter with a worker bound
(including an in-flight join), an operator-pinned filter, and one already
mid-Leave are all left alone. The leave block's own `!active.is_empty()`
guard is widened the same way, so a prover whose every allocation expired
still sweeps.

An unbound allocation stays on-chain Active, so a Leave is valid for it;
only the derived status reads expired.

Not changed: the per-epoch re-confirm still queues every `expired_epoch`
filter, so an orphan is briefly both re-confirmed and proposed for Leave.
Re-confirming re-encodes replicas from local storage, and whether that is
reachable without a bound worker is not established here, so narrowing it
is left out of this fix. The Leave lands within an epoch, after which the
allocation is `Leaving` and out of `expired_epoch`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011K67csMrXEr4ovegwy2Fmq
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