feat(mint): introduce automatic keyset rotations#1058
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1058 +/- ##
==========================================
+ Coverage 74.54% 74.71% +0.17%
==========================================
Files 115 115
Lines 13195 13237 +42
==========================================
+ Hits 9836 9890 +54
+ Misses 3359 3347 -12 ☔ View full report in Codecov by Harness. |
KvngMikey
left a comment
There was a problem hiding this comment.
just the nit, otherwise lgtm!
| while True: | ||
| try: | ||
| await self._check_pending_proofs_and_melt_quotes() | ||
| await self.rotate_keysets_if_needed() |
There was a problem hiding this comment.
The effective rotation time is MINT_KEYSET_ROTATION_INTERVAL_SECONDS + up to MINT_REGULAR_TASKS_INTERVAL_SECONDS, but the latter (a) isn't in .env.example, (b) defaults to 3600s, and (c) is described only as the invoice-checker interval, with no mention of rotation.
It's also overloaded, the same tick drives pending_proof checks. I suggest we either document this coupling in the rotation section of .env.example, or decoupling rotation onto its own timer so ROTATION_INTERVAL_SECONDS for example means what it says.
e9d330e to
ead9cab
Compare
|
Review findings:
Also, rotation is coupled to the shared regular-task loop (ledger lines 161–174), so it can occur up to the regular-task interval late (default 3600 seconds), and can be delayed by pending-proof/melt processing. The basic single-process path and the five new rotation tests pass, but I would request changes for atomicity and concurrency safety. |
callebtc
left a comment
There was a problem hiding this comment.
lack of atomicity and multiple mint race condition found
…move redundant balance parameter
ca983e4 to
5f2ba3c
Compare
Mints with automatic keyset rotation (cashubtc/nutshell#1058) can deactivate their active keyset mid-session. Building outputs on a stale keyset then fails at the mint. Resolve this proactively rather than reacting to the error. Centralize the fix in retryOnceOnSignedOutputs, the wrapper every signature-requesting operation already flows through: add an ensureKeysetsCurrent helper (keys-only fetchMintKeys refresh, then active-keyset resolution and wallet construction from the freshly updated store) and make the wrapper its sole caller. The wrapper now hands each operation a fresh wallet + keyset id, so every signing path (send, redeem, bolt11, bolt12, onchain, melt, sendToLock) builds outputs on the keyset that is active after the refresh. - fetchMintKeys (keys-only) is used instead of updateMintInfoAndKeys, which fetches /v1/info and can trigger a MOTD redirect mid-payment. - bolt12 and onchain previously never refreshed at all (they omitted the update flag); they now refresh via the wrapper. - Drop the redundant, un-awaited second handleOutputsHaveAlreadyBeenSignedError calls in the bolt12/onchain catch blocks; the wrapper already handles that path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This PR introduces automatic keyset rotations for Nutshell to encourage/force operators to rotate keysets regularly, as operators rarely perform them manually.
What's Changed
MINT_KEYSET_ROTATION_ENABLED(default:True) andMINT_KEYSET_ROTATION_INTERVAL_SECONDS(default: 30 days) tocashu/core/settings.pyand documented them in.env.example.should_rotate_keysetto safely calculate the age of active keysets (handling both sqlite and postgres timestamp differences).rotate_keysets_if_neededto automatically rotate active keysets for all configured units when they exceed the interval, while preserving customized fees, orders, and final expiry.store_keysetin DB CRUD to update thevalid_from,valid_to, andfirst_seentimestamps on the in-memory keyset object on creation, ensuring memory and DB stay in perfect synchronization._startup_keysets)._run_regular_tasks).README.md.MINT_DERIVATION_PATHin.envafter a rotation.tests/mint/test_mint_automatic_rotations.pycovering standard rotation, disabled mode, and parameter preservation.Passed
ruff checkandmypywithout any errors.