mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 20:28:23 +00:00
clean up
This commit is contained in:
+2
-2
@@ -36,8 +36,8 @@ HTTP response, `error` is available at the top level and mirrored under
|
||||
| Unexpected invoice creation failure | 500 | `api_error` | `invoice_creation_failed` |
|
||||
|
||||
Request validation failures, including non-positive or excessive amounts, use
|
||||
FastAPI's standard 422 validation response. Only the 503 mint failures are retryable. Use backoff and honor any mint
|
||||
cooldown. The compatibility endpoints `/lightning/*` and
|
||||
FastAPI's standard 422 validation response. Only the 503 mint failures are retryable. Use backoff and honor the
|
||||
`Retry-After` header when present. The compatibility endpoints `/lightning/*` and
|
||||
`/v1/balance/lightning/*` retain their original string `detail` errors and
|
||||
legacy status behavior.
|
||||
|
||||
|
||||
+18
-5
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import hashlib
|
||||
import math
|
||||
import re
|
||||
import secrets
|
||||
import time
|
||||
@@ -244,10 +245,8 @@ async def _request_mint_with_fallback(
|
||||
)
|
||||
continue
|
||||
if all_rate_limited:
|
||||
retry_after = max(
|
||||
(mint_cooldown_remaining(mint) for mint in candidates), default=0.0
|
||||
)
|
||||
raise MintCooldownError("all configured mints", retry_after)
|
||||
slowest = max(candidates, key=mint_cooldown_remaining)
|
||||
raise MintCooldownError(slowest, mint_cooldown_remaining(slowest))
|
||||
raise MintConnectionError(f"All mints failed for request_mint: {tried}")
|
||||
|
||||
|
||||
@@ -279,6 +278,7 @@ def _invoice_error(
|
||||
*,
|
||||
structured: bool,
|
||||
legacy_message: str | None = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
) -> HTTPException:
|
||||
"""Build either the legacy string detail or the v2 typed envelope."""
|
||||
detail: str | dict[str, dict[str, str]]
|
||||
@@ -286,7 +286,18 @@ def _invoice_error(
|
||||
detail = {"error": {"message": message, "type": error_type, "code": code}}
|
||||
else:
|
||||
detail = legacy_message or message
|
||||
return HTTPException(status_code=status_code, detail=detail)
|
||||
return HTTPException(status_code=status_code, detail=detail, headers=headers)
|
||||
|
||||
|
||||
def _mint_retry_after(error: BaseException) -> int | None:
|
||||
current: BaseException | None = error
|
||||
seen: set[int] = set()
|
||||
while current is not None and id(current) not in seen:
|
||||
seen.add(id(current))
|
||||
if isinstance(current, MintCooldownError):
|
||||
return math.ceil(current.retry_after_seconds)
|
||||
current = current.__cause__ or current.__context__
|
||||
return None
|
||||
|
||||
|
||||
def _invoice_creation_error(error: Exception, *, structured: bool) -> HTTPException:
|
||||
@@ -296,12 +307,14 @@ def _invoice_creation_error(error: Exception, *, structured: bool) -> HTTPExcept
|
||||
status_code=500, detail="Failed to create Lightning invoice"
|
||||
)
|
||||
if is_mint_rate_limited(error):
|
||||
retry_after = _mint_retry_after(error)
|
||||
return _invoice_error(
|
||||
503,
|
||||
"Cashu mint rate-limited; retry after cooldown",
|
||||
"mint_rate_limited",
|
||||
"lightning_mint_rate_limited",
|
||||
structured=True,
|
||||
headers={"Retry-After": str(retry_after)} if retry_after else None,
|
||||
)
|
||||
if is_mint_connection_error(error):
|
||||
return _invoice_error(
|
||||
|
||||
@@ -11,6 +11,7 @@ from httpx import AsyncClient
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from routstr.core.db import ApiKey
|
||||
from routstr.mint import MintCooldownError
|
||||
from routstr.wallet import MintConnectionError
|
||||
|
||||
RIP08_PATH = "/lightning/invoice"
|
||||
@@ -317,6 +318,22 @@ async def test_create_invoice_maps_mint_failures(
|
||||
assert resp.json()["detail"]["error"]["code"] == code
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_v2_create_invoice_rate_limited_sets_retry_after(
|
||||
integration_client: AsyncClient,
|
||||
) -> None:
|
||||
with patch(
|
||||
"routstr.lightning.generate_lightning_invoice",
|
||||
side_effect=MintCooldownError("https://mint.example.com", 12.4),
|
||||
):
|
||||
resp = await integration_client.post(V2_PATH, json={"amount_sats": 100})
|
||||
|
||||
assert resp.status_code == 503
|
||||
assert resp.headers["Retry-After"] == "13"
|
||||
assert resp.json()["detail"]["error"]["code"] == "lightning_mint_rate_limited"
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_compatibility_create_failure_keeps_generic_error(
|
||||
|
||||
Reference in New Issue
Block a user