A mint restart strands every outstanding quote in non-current epochs #30

Closed
opened 2026-10-08 22:45:49 +01:00 by vnprc-agent · 0 comments
Collaborator

A mint restart permanently strands every outstanding quote in every non-current epoch. The resume path re-registers only the current epoch's payment processor, so all earlier epochs — including ones still holding paid-but-unissued quotes — become unresolvable and their value can never be collected.

This is the same failure the retire versus deregister work was introduced to prevent, reappearing by a different route. retire keeps the processor map entry, but that map lives only in memory; nothing rebuilds it for retired epochs on startup.

Observed on a live dev stack: integration of vnprc/hashpool#21, #23 and #24, cdk pinned at ed7657c3, with the vnprc/hashpool#29 fix applied.

Evidence

Three epochs existed in epochs.json — 17 (genesis), 122 and 127 (both manual) — and unit 122 still held 5 unissued quotes. The mint was then restarted. Its log shows exactly one registration:

2026-10-08T21:43:37 INFO wait_for_paid_invoices: Starting payment wait task for a runtime-registered processor
2026-10-08T21:43:37 INFO mint::lib::epoch: resuming persisted epoch unit=hash_<pool>_127 height=127

One Starting payment wait task, for epoch 127 alone. Epochs 17 and 122 are not registered. The sweeper's very next pass:

reconciled 81 quote(s) (unit hash_<pool>_122: 5 unissued, unit hash_<pool>_127: 76 unissued)
ERROR unit hash_<pool>_122: batch status check failed for a chunk of 5 quote(s): Unit unsupported
INFO  Minted 392 hash_<pool>_127 from 26 quote(s)
INFO  unit hash_<pool>_127 balance after sweep: 392

The current epoch mints normally. The retired epoch returns Unit unsupported — Error::UnsupportedUnit — for its 5 outstanding quotes, and will do so forever.

Before the restart, those same 5 quotes did not produce Unit unsupported: across every pass they reported only a wallet-side Quote already in use by another operation, confirming the mint still resolved the retired unit while the process lived. The regression is introduced by the restart, not by retirement.

Where it comes from

roles/mint/src/lib/epoch/mod.rs resume path registers a processor for the current epoch only. An earlier review of vnprc/hashpool#24 caught the adjacent case — a restart failing to re-register the resumed epoch at all, fixed in 922637e9 — but that fix covers the current epoch, and nothing covers epochs that are closed yet still referenced by live quotes.

The in-memory nature of the map is the crux. retire_payment_processor deliberately keeps the map entry while dropping the NUT-04/NUT-05 settings, which is what makes a closed epoch resolvable but not quotable. A restart rebuilds the map from configuration and the resumed epoch, so that carefully preserved entry is simply absent.

Scope

In scope: on resume, restore every non-dissolved epoch's processor, not just the current one. The semantics already exist and compose cleanly — register each epoch's processor, then retire every one except the current. That reproduces the intended post-rotation state exactly: all epochs resolvable for outstanding quotes, only the current epoch quotable.

Deciding which epochs are worth restoring is part of the work. Restoring every record retains a map entry per epoch forever, which grows without bound; restoring only those with outstanding unissued quotes is tighter but requires the mint to ask that question at startup. Either is defensible; the choice should be explicit rather than incidental.

Out of scope: the wallet-side reservation leak noted on vnprc/hashpool#29, and the unit hash: failed to refresh keysets on first sight: Unknown Keyset warning the sweeper emits for the retired static base unit, which is noise rather than a fault but will mislead a reader of these logs.

Validation

With several epochs recorded and outstanding unissued quotes in a non-current one, restart the mint and confirm the sweeper's batch status check for that epoch does not return Unit unsupported, and that its quotes go on to mint. Then confirm new quote creation for that retired epoch still fails, so restoring resolvability has not accidentally reopened a closed epoch for issuance.

A mint restart permanently strands every outstanding quote in every non-current epoch. The resume path re-registers only the current epoch's payment processor, so all earlier epochs — including ones still holding paid-but-unissued quotes — become unresolvable and their value can never be collected. This is the same failure the `retire` versus `deregister` work was introduced to prevent, reappearing by a different route. `retire` keeps the processor map entry, but that map lives only in memory; nothing rebuilds it for retired epochs on startup. Observed on a live dev stack: integration of vnprc/hashpool#21, #23 and #24, cdk pinned at `ed7657c3`, with the vnprc/hashpool#29 fix applied. ### Evidence Three epochs existed in `epochs.json` — 17 (genesis), 122 and 127 (both manual) — and unit 122 still held 5 unissued quotes. The mint was then restarted. Its log shows exactly one registration: ``` 2026-10-08T21:43:37 INFO wait_for_paid_invoices: Starting payment wait task for a runtime-registered processor 2026-10-08T21:43:37 INFO mint::lib::epoch: resuming persisted epoch unit=hash_<pool>_127 height=127 ``` One `Starting payment wait task`, for epoch 127 alone. Epochs 17 and 122 are not registered. The sweeper's very next pass: ``` reconciled 81 quote(s) (unit hash_<pool>_122: 5 unissued, unit hash_<pool>_127: 76 unissued) ERROR unit hash_<pool>_122: batch status check failed for a chunk of 5 quote(s): Unit unsupported INFO Minted 392 hash_<pool>_127 from 26 quote(s) INFO unit hash_<pool>_127 balance after sweep: 392 ``` The current epoch mints normally. The retired epoch returns `Unit unsupported` — `Error::UnsupportedUnit` — for its 5 outstanding quotes, and will do so forever. Before the restart, those same 5 quotes did **not** produce `Unit unsupported`: across every pass they reported only a wallet-side `Quote already in use by another operation`, confirming the mint still resolved the retired unit while the process lived. The regression is introduced by the restart, not by retirement. ### Where it comes from `roles/mint/src/lib/epoch/mod.rs` resume path registers a processor for the current epoch only. An earlier review of vnprc/hashpool#24 caught the adjacent case — a restart failing to re-register the resumed epoch at all, fixed in `922637e9` — but that fix covers the current epoch, and nothing covers epochs that are closed yet still referenced by live quotes. The in-memory nature of the map is the crux. `retire_payment_processor` deliberately keeps the map entry while dropping the NUT-04/NUT-05 settings, which is what makes a closed epoch resolvable but not quotable. A restart rebuilds the map from configuration and the resumed epoch, so that carefully preserved entry is simply absent. ### Scope In scope: on resume, restore every non-dissolved epoch's processor, not just the current one. The semantics already exist and compose cleanly — register each epoch's processor, then retire every one except the current. That reproduces the intended post-rotation state exactly: all epochs resolvable for outstanding quotes, only the current epoch quotable. Deciding which epochs are worth restoring is part of the work. Restoring every record retains a map entry per epoch forever, which grows without bound; restoring only those with outstanding unissued quotes is tighter but requires the mint to ask that question at startup. Either is defensible; the choice should be explicit rather than incidental. Out of scope: the wallet-side reservation leak noted on vnprc/hashpool#29, and the `unit hash: failed to refresh keysets on first sight: Unknown Keyset` warning the sweeper emits for the retired static base unit, which is noise rather than a fault but will mislead a reader of these logs. ### Validation With several epochs recorded and outstanding unissued quotes in a non-current one, restart the mint and confirm the sweeper's batch status check for that epoch does **not** return `Unit unsupported`, and that its quotes go on to mint. Then confirm new quote creation for that retired epoch still fails, so restoring resolvability has not accidentally reopened a closed epoch for issuance.
vnprc closed this issue 2026-10-09 06:30:43 +01:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
vnprc/hashpool#30
No description provided.