-
-
Notifications
You must be signed in to change notification settings - Fork 173
feat(mint): introduce automatic keyset rotations #1058
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
a1denvalu3
wants to merge
8
commits into
main
Choose a base branch
from
automatic-keyset-rotations
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+401
−9
Open
Changes from 1 commit
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
38caf8b
feat(mint): introduce automatic keyset rotations
a1denvalu3 0a3fffa
fix(mint): preserve keyset redemption grace period on automatic rotation
a1denvalu3 daef7a1
docs(mint): document multiple formats in _parse_valid_from
a1denvalu3 d5eb510
fix(mint): update default keyset and check rotations on key requests
a1denvalu3 4c788ba
test(mint): add background keyset rotation test and remove on-demand …
a1denvalu3 52843a9
update default rotation interval to 90 days
a1denvalu3 7d60dda
test(mint): fix flakiness and incorrect assertions in keyset rotation…
a1denvalu3 e9d330e
docs(mint): update default keyset rotation interval to 90 days and re…
a1denvalu3 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import asyncio | ||
| import datetime | ||
|
|
||
| import pytest | ||
|
|
||
| from cashu.core.settings import settings | ||
| from cashu.mint.ledger import Ledger | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_should_rotate_keyset_behavior(ledger: Ledger): | ||
| # Get any active keyset | ||
| keyset = next(k for k in ledger.keysets.values() if k.active) | ||
|
|
||
| # By default, freshly created keyset should not rotate | ||
| assert not ledger.should_rotate_keyset(keyset) | ||
|
|
||
| # If keyset is inactive, it should never rotate | ||
| keyset.active = False | ||
| assert not ledger.should_rotate_keyset(keyset) | ||
| keyset.active = True | ||
|
|
||
| # If valid_from is mocked in the far past, it should rotate | ||
| original_valid_from = keyset.valid_from | ||
| keyset.valid_from = (datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=31)).strftime( | ||
| "%Y-%m-%d %H:%M:%S" | ||
| ) | ||
| assert ledger.should_rotate_keyset(keyset) | ||
|
|
||
| # Restore | ||
| keyset.valid_from = original_valid_from | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_automatic_keyset_rotation_flow(ledger: Ledger): | ||
| # Set a very short rotation interval | ||
| original_interval = settings.mint_keyset_rotation_interval_seconds | ||
| original_enabled = settings.mint_keyset_rotation_enabled | ||
|
|
||
| try: | ||
| settings.mint_keyset_rotation_enabled = True | ||
| settings.mint_keyset_rotation_interval_seconds = 1 | ||
|
|
||
| # Keep track of active keysets before rotation | ||
| active_keysets_before = { | ||
| k.unit: k for k in ledger.keysets.values() if k.active | ||
| } | ||
| assert len(active_keysets_before) > 0 | ||
|
|
||
| # Wait to exceed the 1 second interval | ||
| await asyncio.sleep(1.5) | ||
|
|
||
| # Trigger automatic rotation check | ||
| await ledger.rotate_keysets_if_needed() | ||
|
|
||
| # Get active keysets after rotation | ||
| active_keysets_after = { | ||
| k.unit: k for k in ledger.keysets.values() if k.active | ||
| } | ||
|
|
||
| for unit, old_keyset in active_keysets_before.items(): | ||
| new_keyset = active_keysets_after[unit] | ||
| # Verify a new keyset has been created and it differs from the old one | ||
| assert old_keyset.id != new_keyset.id | ||
|
|
||
| # Verify the old keyset is now inactive in memory and DB | ||
| assert not old_keyset.active | ||
| db_old_keysets = await ledger.crud.get_keyset(db=ledger.db, id=old_keyset.id) | ||
| assert len(db_old_keysets) == 1 | ||
| assert not db_old_keysets[0].active | ||
|
|
||
| # Verify new keyset is active in memory and DB | ||
| assert new_keyset.active | ||
| db_new_keysets = await ledger.crud.get_keyset(db=ledger.db, id=new_keyset.id) | ||
| assert len(db_new_keysets) == 1 | ||
| assert db_new_keysets[0].active | ||
|
|
||
| # Verify key parameters are preserved | ||
| assert new_keyset.input_fee_ppk == old_keyset.input_fee_ppk | ||
| assert len(new_keyset.amounts) == len(old_keyset.amounts) | ||
|
|
||
| # Verify derivation path counter has incremented | ||
| old_path = old_keyset.derivation_path.split("/") | ||
| new_path = new_keyset.derivation_path.split("/") | ||
| assert old_path[:-1] == new_path[:-1] | ||
| assert int(new_path[-1].replace("'", "")) - int(old_path[-1].replace("'", "")) == 1 | ||
|
|
||
| finally: | ||
| # Restore settings | ||
| settings.mint_keyset_rotation_interval_seconds = original_interval | ||
| settings.mint_keyset_rotation_enabled = original_enabled | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_automatic_keyset_rotation_disabled(ledger: Ledger): | ||
| # Keep track of active keyset | ||
| keyset = next(k for k in ledger.keysets.values() if k.active) | ||
|
|
||
| original_interval = settings.mint_keyset_rotation_interval_seconds | ||
| original_enabled = settings.mint_keyset_rotation_enabled | ||
|
|
||
| try: | ||
| settings.mint_keyset_rotation_enabled = False | ||
| settings.mint_keyset_rotation_interval_seconds = 1 | ||
|
|
||
| # Wait to exceed interval | ||
| await asyncio.sleep(1.5) | ||
|
|
||
| # Trigger check (should do nothing since disabled) | ||
| await ledger.rotate_keysets_if_needed() | ||
|
|
||
| # Get active keyset for the same unit | ||
| active_keysets = [k for k in ledger.keysets.values() if k.active and k.unit == keyset.unit] | ||
| assert len(active_keysets) == 1 | ||
| assert active_keysets[0].id == keyset.id | ||
| assert active_keysets[0].active | ||
|
|
||
| finally: | ||
| settings.mint_keyset_rotation_interval_seconds = original_interval | ||
| settings.mint_keyset_rotation_enabled = original_enabled |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.