From 7aff25ac68192abb8012afcc2660f5209cdfe0fb Mon Sep 17 00:00:00 2001 From: 9qeklajc Date: Thu, 3 Sep 2026 22:15:27 +0200 Subject: [PATCH] Remove remaining child-key cleanup residue --- docs/api/authentication.md | 16 ---------------- routstr/auth.py | 6 +++--- routstr/core/admin.py | 2 +- routstr/core/main.py | 5 ----- tests/integration/test_payment_invariants.py | 4 ++-- tests/integration/test_prune_dead_api_keys.py | 2 +- tests/integration/test_temporary_balances_api.py | 2 +- tests/integration/test_wallet_topup.py | 7 ++----- tests/unit/test_ehbp_finalize_payment.py | 6 +++--- tests/unit/test_lightning_settlement.py | 2 -- tests/unit/test_stale_reservations.py | 2 +- ui/components/landing/cashu-payment-workflow.tsx | 3 ++- 12 files changed, 16 insertions(+), 41 deletions(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index ec4c1449..387481cf 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -371,22 +371,6 @@ Content-Type: application/json ## Monitoring -### Usage Alerts - -Set up usage notifications: - -```bash -POST /v1/wallet/alerts -Authorization: Bearer sk-... -Content-Type: application/json - -{ - "low_balance_threshold": 1000, - "daily_spend_limit": 5000, - "webhook_url": "https://your-app.com/webhook" -} -``` - ### Audit Logging All API key usage is logged: diff --git a/routstr/auth.py b/routstr/auth.py index 0478dfa8..e3faeed3 100644 --- a/routstr/auth.py +++ b/routstr/auth.py @@ -206,7 +206,7 @@ async def _validate_bearer_key_locked( Validates the provided API key using SQLModel. If it's a cashu key, it redeems it and stores its hash and balance. Otherwise checks if the hash of the key exists. - Includes a balance check against min_cost for limited keys. + Checks the key's available balance against min_cost when required. """ logger.debug( "Starting bearer key validation", @@ -1347,8 +1347,8 @@ async def adjust_payment_for_tokens( # actual cost exceeded discounted reservation (due to tolerance_percentage) if cost_difference > 0: - # Lock the billing row so the parent and child record the same - # database-determined charge under concurrent finalizations. + # Lock the key row so concurrent finalizations use the same + # database-determined charge. actual_charge_msats = 0 for attempt in range(5): locked_billing_key = ( diff --git a/routstr/core/admin.py b/routstr/core/admin.py index 46231554..d5eba742 100644 --- a/routstr/core/admin.py +++ b/routstr/core/admin.py @@ -111,7 +111,7 @@ async def get_temporary_balances_api( ) total = count_result.one() - # Aggregate totals across the whole (search-filtered) set, not just the + # Aggregate totals across the whole search-filtered set, not just this page. balance_totals_result = await session.exec( select( func.coalesce(func.sum(ApiKey.balance), 0), diff --git a/routstr/core/main.py b/routstr/core/main.py index 07f11aa2..616a1c29 100644 --- a/routstr/core/main.py +++ b/routstr/core/main.py @@ -63,7 +63,6 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]: models_refresh_task = None model_maps_refresh_task = None model_paths_refresh_task = None - key_reset_task = None stale_reservation_task = None dead_key_prune_task = None auto_topup_task = None @@ -187,8 +186,6 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]: model_maps_refresh_task.cancel() if model_paths_refresh_task is not None: model_paths_refresh_task.cancel() - if key_reset_task is not None: - key_reset_task.cancel() if stale_reservation_task is not None: stale_reservation_task.cancel() if dead_key_prune_task is not None: @@ -222,8 +219,6 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]: tasks_to_wait.append(model_maps_refresh_task) if model_paths_refresh_task is not None: tasks_to_wait.append(model_paths_refresh_task) - if key_reset_task is not None: - tasks_to_wait.append(key_reset_task) if stale_reservation_task is not None: tasks_to_wait.append(stale_reservation_task) if dead_key_prune_task is not None: diff --git a/tests/integration/test_payment_invariants.py b/tests/integration/test_payment_invariants.py index cb10b80a..6209aff9 100644 --- a/tests/integration/test_payment_invariants.py +++ b/tests/integration/test_payment_invariants.py @@ -3,8 +3,8 @@ Every finalization branch of ``adjust_payment_for_tokens`` must respect the same accounting rules: a completed request is charged exactly once, its reported ``charged_msats`` matches the actual debit, it never spends more than -its own reservation leaves available, and child keys spend their parent's -balance without raiding sibling reservations. +its own reservation leaves available, and concurrent requests cannot raid each +other's reservations. """ import asyncio diff --git a/tests/integration/test_prune_dead_api_keys.py b/tests/integration/test_prune_dead_api_keys.py index d0987af9..aaacecad 100644 --- a/tests/integration/test_prune_dead_api_keys.py +++ b/tests/integration/test_prune_dead_api_keys.py @@ -45,7 +45,7 @@ def _dead_key(created_at: int | None) -> ApiKey: @pytest.mark.asyncio async def test_prunes_old_refunded_zero_key(patched_db_engine: None) -> None: - """A funded-then-refunded key (0/0/0, NULL parent, old) is pruned.""" + """A funded-then-refunded zero-balance key with an old timestamp is pruned.""" key = _dead_key(LONG_AGO) async with create_session() as session: session.add(key) diff --git a/tests/integration/test_temporary_balances_api.py b/tests/integration/test_temporary_balances_api.py index a6a2d5e5..80c0e627 100644 --- a/tests/integration/test_temporary_balances_api.py +++ b/tests/integration/test_temporary_balances_api.py @@ -129,7 +129,7 @@ async def test_temporary_balances_totals( ) -> None: await _add_key( integration_session, - "parent", + "standalone_key", balance=5000, total_spent=100, total_requests=3, diff --git a/tests/integration/test_wallet_topup.py b/tests/integration/test_wallet_topup.py index 37d59651..3a7bff6f 100644 --- a/tests/integration/test_wallet_topup.py +++ b/tests/integration/test_wallet_topup.py @@ -337,16 +337,13 @@ async def test_topup_during_active_proxy_request( # type: ignore[no-untyped-def @pytest.mark.integration @pytest.mark.asyncio -async def test_maximum_balance_limits( # type: ignore[no-untyped-def] +async def test_large_balance_topup_is_allowed( # type: ignore[no-untyped-def] integration_client: AsyncClient, authenticated_client: AsyncClient, testmint_wallet: Any, integration_session, ) -> None: - """Test if there are any maximum balance limits""" - - # Note: The current implementation doesn't enforce maximum balance limits - # This test verifies large balances are handled correctly + """Test that a large top-up is accepted and reflected in the balance.""" # Get current balance response = await authenticated_client.get("/v1/wallet/") diff --git a/tests/unit/test_ehbp_finalize_payment.py b/tests/unit/test_ehbp_finalize_payment.py index 1ed2dda4..2f905e63 100644 --- a/tests/unit/test_ehbp_finalize_payment.py +++ b/tests/unit/test_ehbp_finalize_payment.py @@ -134,11 +134,11 @@ async def test_unmeasured_ehbp_releases_reservation( @pytest.mark.asyncio -async def test_finalize_actual_cost_payment_rolls_back_when_parent_update_matches_no_rows( +async def test_finalize_actual_cost_payment_rolls_back_when_billing_key_update_matches_no_rows( session: AsyncSession, monkeypatch: pytest.MonkeyPatch, ) -> None: - key = ApiKey(hashed_key="ehbp-missing-parent", balance=10_000) + key = ApiKey(hashed_key="ehbp-failed-billing-update", balance=10_000) session.add(key) await session.commit() await pay_for_request(key, 3_000, session) @@ -158,7 +158,7 @@ async def test_finalize_actual_cost_payment_rolls_back_when_parent_update_matche assert charged == 0 rollback_spy.assert_awaited_once() - updated = await _api_key(session, "ehbp-missing-parent") + updated = await _api_key(session, "ehbp-failed-billing-update") assert updated is not None assert updated.balance == 10_000 assert updated.reserved_balance == 0 diff --git a/tests/unit/test_lightning_settlement.py b/tests/unit/test_lightning_settlement.py index eddfde57..8d7c96a9 100644 --- a/tests/unit/test_lightning_settlement.py +++ b/tests/unit/test_lightning_settlement.py @@ -41,8 +41,6 @@ def _invoice(**overrides: object) -> SimpleNamespace: "paid_at": None, "api_key_hash": None, "mint_url": "http://mint:3338", - "balance_limit": None, - "balance_limit_reset": None, "validity_date": None, "created_at": 1, "expires_at": 2, diff --git a/tests/unit/test_stale_reservations.py b/tests/unit/test_stale_reservations.py index 73b3e43f..47061dda 100644 --- a/tests/unit/test_stale_reservations.py +++ b/tests/unit/test_stale_reservations.py @@ -1,7 +1,7 @@ """Tests for stale reserved_balance handling (issue #551). Covers: -- pay_for_request stamping reserved_at on billing and child keys +- pay_for_request stamping reserved_at on charged keys - release_stale_reservations sweeper semantics - reset_all_reserved_balances clearing reserved_at - refund endpoint self-healing stale/legacy reservations diff --git a/ui/components/landing/cashu-payment-workflow.tsx b/ui/components/landing/cashu-payment-workflow.tsx index 9eea7f6e..d1ac23a6 100644 --- a/ui/components/landing/cashu-payment-workflow.tsx +++ b/ui/components/landing/cashu-payment-workflow.tsx @@ -245,7 +245,8 @@ export function CashuPaymentWorkflow({ Redeems instantly and returns sk- key.
- Optional limits can be set above for enhanced security. + Set an optional validity date above to expire the key + automatically.