translator: discover quotes by pubkey and sweep every per-epoch unit #32

Merged
vnprc merged 2 commits from agent/nutxx-pubkey-lookup into master 2026-10-09 06:18:04 +01:00
Collaborator

Mining rewards were being lost silently, in two ways, and this fixes both. On a live dev stack it now mines shares, mints ecash in the genesis epoch to a balance of 2752, keeps minting across two epoch rotations, and drains a backlog of 55 previously stuck rewards in a single pass. If this is wrong, revert the two commits: the wallet DB schema is untouched and master stays functional on its current cdk pin.

Replaces vnprc/hashpool#21, closed only so the history could be squashed; the forge refuses force-push on an open pull request branch. The tree is byte-identical to the state verified there. It also carries the multi-unit sweeper, which merged into this branch as vnprc/hashpool#31 — hence the retitle.

Merges to master. vnprc/hashpool#24 follows.

In plain English

The pool's wallet has to know which rewards it is owed before it can collect them. It used to be told, one message per reward, and a lost message meant that reward was never collected and nothing reported it. The wallet now asks the mint instead, so a lost message costs seconds rather than money.

Separately, once the mint issues a different kind of ecash each epoch, the wallet has to collect all of them. It could only collect the first, so every epoch after the first lost everything it earned. It now collects every kind it sees, working through a backlog in bounded batches so one bad reward cannot block the rest.

Two more faults turned up only by running the real stack: on first seeing a new kind of ecash the wallet never asked the mint for the matching key, so collection failed forever; and a failed attempt left the reward marked "in progress" with nothing to clear it, so it could never be retried. Both fixed here, because a new epoch cannot be collected without them.

Commits

  • 910f1f32 moves the cdk dependency to the forge and pins it, including the CurrencyUnit::Custom signature adaptation and the cargo fetch setting without which the repository does not build. Builds alone.
  • 96c766ab is the translator work: pubkey discovery, per-epoch unit sweeping, the keyset refresh, and the reservation cleanup.

Twenty-seven commits became two. They are grouped as dependency then application code rather than one-per-fix, because the pin must land first — the sweeper calls a cdk API that does not exist in the older revision — and because the two translator features are interleaved in one file, so splitting them would mean hunk surgery rather than a meaningful boundary.

Risk

R2. The cdk pin moves in all five sites that must agree and resolves in roles/Cargo.lock; MSRV-pinned transitive deps are unchanged (time 0.3.41, serde_with 3.17.0, darling 0.21.3, simple_asn1 0.6.3 against rustc 1.86.0), and a fresh re-resolve would pick time 0.3.55 and fail. Per-unit wallet handles share one sqlite localstore and one metadata cache; the schema and NUT-13 counters are unit- or keyset-scoped and units are swept sequentially, so there is no cross-unit contention. A unit whose keyset refresh fails is deferred to the next pass, so a mint outage delays that unit rather than leaking reservations. The reconcile log format changed to a per-unit summary; anything scraping the old string needs updating. The SV2 quote-id fetch path is retained but disabled, so discovery is sweep-only.

Validation

  • cargo check -p mint clean on the first commit alone; cargo test -p translator_sv2 37 passed, 0 failed on the tip, re-run by the integrator.
  • Live dev stack with vnprc/hashpool#24: shares mined, ecash minted, two rotations, mint restarted, and 55 wedged rewards cleared and minted in one pass — five of them from an epoch that had already closed.
  • Four cold cross-family review passes across this work and its fixes. Findings included recovery resuming the faucet's in-flight transfers, a failed key lookup being ignored before reserving rewards, and a fix that traded that hazard for a permanent leak on the shared wallet. Each was fixed and re-reviewed; comment bloat was cut on every pass, including dead commented-out code removed from the notification handler.

Not re-established: the original 2026-08-10 live run, which was performed against a cdk revision that no longer exists anywhere and so cannot be diffed against the current pin. The run above supersedes it.

Mining rewards were being lost silently, in two ways, and this fixes both. On a live dev stack it now mines shares, mints ecash in the genesis epoch to a balance of 2752, keeps minting across two epoch rotations, and drains a backlog of 55 previously stuck rewards in a single pass. If this is wrong, revert the two commits: the wallet DB schema is untouched and master stays functional on its current cdk pin. Replaces vnprc/hashpool#21, closed only so the history could be squashed; the forge refuses force-push on an open pull request branch. The tree is byte-identical to the state verified there. It also carries the multi-unit sweeper, which merged into this branch as vnprc/hashpool#31 — hence the retitle. Merges to master. vnprc/hashpool#24 follows. ## In plain English The pool's wallet has to know which rewards it is owed before it can collect them. It used to be told, one message per reward, and a lost message meant that reward was never collected and nothing reported it. The wallet now asks the mint instead, so a lost message costs seconds rather than money. Separately, once the mint issues a different kind of ecash each epoch, the wallet has to collect all of them. It could only collect the first, so every epoch after the first lost everything it earned. It now collects every kind it sees, working through a backlog in bounded batches so one bad reward cannot block the rest. Two more faults turned up only by running the real stack: on first seeing a new kind of ecash the wallet never asked the mint for the matching key, so collection failed forever; and a failed attempt left the reward marked "in progress" with nothing to clear it, so it could never be retried. Both fixed here, because a new epoch cannot be collected without them. ## Commits - `910f1f32` moves the cdk dependency to the forge and pins it, including the `CurrencyUnit::Custom` signature adaptation and the cargo fetch setting without which the repository does not build. Builds alone. - `96c766ab` is the translator work: pubkey discovery, per-epoch unit sweeping, the keyset refresh, and the reservation cleanup. Twenty-seven commits became two. They are grouped as dependency then application code rather than one-per-fix, because the pin must land first — the sweeper calls a cdk API that does not exist in the older revision — and because the two translator features are interleaved in one file, so splitting them would mean hunk surgery rather than a meaningful boundary. ## Risk R2. The cdk pin moves in all five sites that must agree and resolves in `roles/Cargo.lock`; MSRV-pinned transitive deps are unchanged (`time` 0.3.41, `serde_with` 3.17.0, `darling` 0.21.3, `simple_asn1` 0.6.3 against rustc 1.86.0), and a fresh re-resolve would pick `time` 0.3.55 and fail. Per-unit wallet handles share one sqlite localstore and one metadata cache; the schema and NUT-13 counters are unit- or keyset-scoped and units are swept sequentially, so there is no cross-unit contention. A unit whose keyset refresh fails is deferred to the next pass, so a mint outage delays that unit rather than leaking reservations. The reconcile log format changed to a per-unit summary; anything scraping the old string needs updating. The SV2 quote-id fetch path is retained but disabled, so discovery is sweep-only. ## Validation - `cargo check -p mint` clean on the first commit alone; `cargo test -p translator_sv2` 37 passed, 0 failed on the tip, re-run by the integrator. - Live dev stack with vnprc/hashpool#24: shares mined, ecash minted, two rotations, mint restarted, and 55 wedged rewards cleared and minted in one pass — five of them from an epoch that had already closed. - Four cold cross-family review passes across this work and its fixes. Findings included recovery resuming the faucet's in-flight transfers, a failed key lookup being ignored before reserving rewards, and a fix that traded that hazard for a permanent leak on the shared wallet. Each was fixed and re-reviewed; comment bloat was cut on every pass, including dead commented-out code removed from the notification handler. Not re-established: the original 2026-08-10 live run, which was performed against a cdk revision that no longer exists anywhere and so cannot be diffed against the current pin. The run above supersedes it.
The cdk fork now lives at the forge rather than only on GitHub. Two revisions
this repository previously pinned were lost because they were authored locally
and never published, and GitHub cannot be pushed to without a human; the forge
can, so work lands when it is made.

Moves all five pin sites to the forge URL and the revision 874a1bf3, which
carries the NUT-XX pubkey lookup, the `only_mintable` filter, dynamic payment
processor registration with retire, and the narrow orphaned-reservation
cleanup. `.cargo/config.toml` sets `net.git-fetch-with-cli` because cargo's
bundled libgit2 cannot authenticate to the forge over SSH; without it the
repository does not build at all.

`CurrencyUnit::Custom` now takes `Arc<str>`, so the call sites in
`protocols/ehash` and `roles/mint` adapt. No behaviour changes.

The MSRV-pinned transitive deps are unchanged: time 0.3.41, serde_with 3.17.0,
darling 0.21.3, simple_asn1 0.6.3 against rustc 1.86.0.
Two faults cost mining rewards silently, and both are fixed here because the
sweep path is one piece of code.

Discovery depended on a per-quote SV2 notification from the mint. A dropped
message meant that quote's ehash was never minted and nothing said so. The
wallet now asks instead: a reconcile pass queries the mint for every quote
locked to its key, so a lost notification costs a few seconds rather than the
reward. The lookup is unit-agnostic, which is what lets one wallet discover
every epoch's quotes. The SV2 quote-id fetch is retained but disabled.

The sweeper then only minted the single unit its wallet was built with, so
after the first epoch rotation it fetched the new unit's quotes, stored them,
and never minted them. It now mints every unit it sees: a per-unit wallet
handle is built on first sight over one shared localstore, seed and metadata
cache, and each unit runs the same status-check then P2PK-locked batch mint.
Discovery unions the pubkey lookup with a unit-blind read of unissued quotes,
so a failing lookup endpoint cannot hide a backlog. Work is bounded per unit
per pass -- at most 200 quotes, locally-mintable first, in chunks of 50 -- so
one bad quote id wedges at most its own chunk.

Two further faults, found by running the full dev stack rather than by any
test, are fixed because a new epoch cannot be minted without them. The shared
metadata cache is keyed by mint, not unit, and read as fresh while a new
epoch's keyset was absent, so every mint failed with "Unknown Keyset" forever;
first sight of a unit now forces one refresh, and a unit whose refresh fails is
skipped for the pass rather than having quotes reserved behind a keyset that
could not be looked up. A failed batch mint also left the quote reserved with
nothing to release it; each pass now clears orphaned reservations through cdk's
narrow cleanup, which never resumes a saga and so is safe on the base unit's
handle even though the faucet drives its own Send saga over it.

The faucet, web UI and notification path keep using the base-unit wallet as
before. The wallet DB schema is untouched.

Closes vnprc/hashpool#7. Closes vnprc/hashpool#17. Closes vnprc/hashpool#29.
Refs vnprc/hashpool#13.
vnprc approved these changes 2026-10-09 06:17:58 +01:00
vnprc merged commit 96c766ab35 into master 2026-10-09 06:18:04 +01:00
vnprc deleted branch agent/nutxx-pubkey-lookup 2026-10-09 06:18:04 +01:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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!32
No description provided.