From 4723b9db4d442a49fe7be41a4505178262ebe964 Mon Sep 17 00:00:00 2001 From: 9qeklajc Date: Sat, 14 Mar 2026 16:56:44 +0100 Subject: [PATCH] fix storing in/out trans. --- routstr/auth.py | 10 +-- routstr/core/admin.py | 10 ++- routstr/core/db.py | 18 +++--- routstr/proxy.py | 7 +-- routstr/upstream/base.py | 129 ++++++++++++++++++++++++++------------- 5 files changed, 114 insertions(+), 60 deletions(-) diff --git a/routstr/auth.py b/routstr/auth.py index 07d83dff..002f71da 100644 --- a/routstr/auth.py +++ b/routstr/auth.py @@ -372,12 +372,12 @@ async def validate_bearer_key( }, ) + key_preview = bearer_key[:10] + "..." if len(bearer_key) > 10 else bearer_key logger.error( - "Invalid API key format", + f"Invalid API key format: preview={key_preview!r} length={len(bearer_key)} " + f"(expected 'sk-...' or 'cashu...' token)", extra={ - "key_preview": bearer_key[:10] + "..." - if len(bearer_key) > 10 - else bearer_key, + "key_preview": key_preview, "key_length": len(bearer_key), }, ) @@ -386,7 +386,7 @@ async def validate_bearer_key( status_code=401, detail={ "error": { - "message": "Invalid API key", + "message": "Invalid API key format. Expected an 'sk-...' API key or a 'cashu...' token.", "type": "invalid_request_error", "code": "invalid_api_key", } diff --git a/routstr/core/admin.py b/routstr/core/admin.py index 6884a959..f13603e0 100644 --- a/routstr/core/admin.py +++ b/routstr/core/admin.py @@ -16,7 +16,13 @@ from ..wallet import ( send_token, slow_filter_spend_proofs, ) -from .db import ApiKey, CashuTransaction, ModelRow, UpstreamProviderRow, create_session +from .db import ( + ApiKey, + CashuTransaction, + ModelRow, + UpstreamProviderRow, + create_session, +) from .log_manager import log_manager from .logging import get_logger from .settings import SettingsService, settings @@ -1234,7 +1240,7 @@ async def get_transactions_api( stmt = stmt.where(CashuTransaction.swept == True) # noqa: E712 elif status == "pending": stmt = stmt.where( - not CashuTransaction.collected, + CashuTransaction.collected == False, CashuTransaction.swept == False, # noqa: E712 ) diff --git a/routstr/core/db.py b/routstr/core/db.py index 50f935b8..90258dbb 100644 --- a/routstr/core/db.py +++ b/routstr/core/db.py @@ -2,6 +2,7 @@ import os import pathlib import sqlite3 import time +import uuid from contextlib import asynccontextmanager from typing import AsyncGenerator @@ -134,7 +135,8 @@ class CashuTransaction(SQLModel, table=True): # type: ignore id: str = Field( primary_key=True, - description="SHA-256 hash of the x-cashu token or unique identifier", + default_factory=lambda: uuid.uuid4().hex, + description="Unique transaction identifier", ) token: str = Field(description="Serialized Cashu token") amount: int = Field(description="Amount in the token's unit") @@ -151,31 +153,33 @@ class CashuTransaction(SQLModel, table=True): # type: ignore async def store_cashu_transaction( - id: str, token: str, amount: int, unit: str, mint_url: str | None = None, - type: str = "out", + typ: str = "out", request_id: str | None = None, + collected: bool = False, + created_at: int | None = None, ) -> None: try: async with create_session() as session: tx = CashuTransaction( - id=id, token=token, amount=amount, unit=unit, mint_url=mint_url, - type=type, + type=typ, request_id=request_id, + collected=collected, + created_at=created_at or int(time.time()), ) session.add(tx) await session.commit() except Exception as e: logger.warning( - "Failed to store cashu transaction", - extra={"error": str(e), "id": id, "type": type}, + f"Failed to store cashu transaction: {e} (type={typ})", + extra={"error": str(e), "type": typ}, ) diff --git a/routstr/proxy.py b/routstr/proxy.py index b9ba6e1d..c884c896 100644 --- a/routstr/proxy.py +++ b/routstr/proxy.py @@ -455,15 +455,14 @@ async def get_bearer_token_key( ) return key except Exception as e: + key_preview = bearer_key[:20] + "..." if len(bearer_key) > 20 else bearer_key logger.error( - "Bearer token validation failed", + f"Bearer token validation failed: {type(e).__name__}: {e} path={path} key={key_preview!r}", extra={ "error": str(e), "error_type": type(e).__name__, "path": path, - "bearer_key_preview": bearer_key[:20] + "..." - if len(bearer_key) > 20 - else bearer_key, + "bearer_key_preview": key_preview, }, ) raise diff --git a/routstr/upstream/base.py b/routstr/upstream/base.py index cf2a2fe3..44785e28 100644 --- a/routstr/upstream/base.py +++ b/routstr/upstream/base.py @@ -1686,6 +1686,7 @@ class BaseUpstreamProvider: unit: str, mint: str | None = None, payment_token_hash: str | None = None, + request_id: str | None = None, ) -> str: """Create and send a refund token to the user. @@ -1694,6 +1695,7 @@ class BaseUpstreamProvider: unit: Unit of the refund (sat or msat) mint: Optional mint URL for the refund token payment_token_hash: Optional SHA-256 hash of the original payment token for storage + request_id: Optional HTTP request ID for tracking Returns: Refund token string @@ -1723,18 +1725,17 @@ class BaseUpstreamProvider: }, ) - if payment_token_hash: - try: - await store_cashu_transaction( - id=payment_token_hash, - token=refund_token, - amount=amount, - unit=unit, - mint_url=mint, - type="out", - ) - except Exception: - pass # store_cashu_transaction already logs + try: + await store_cashu_transaction( + token=refund_token, + amount=amount, + unit=unit, + mint_url=mint, + typ="out", + request_id=request_id, + ) + except Exception: + pass # store_cashu_transaction already logs return refund_token except Exception as e: @@ -1786,6 +1787,7 @@ class BaseUpstreamProvider: max_cost_for_model: int, mint: str | None = None, payment_token_hash: str | None = None, + request_id: str | None = None, ) -> StreamingResponse: """Handle streaming response for X-Cashu payment, calculating refund if needed. @@ -1868,7 +1870,8 @@ class BaseUpstreamProvider: ) refund_token = await self.send_refund( - refund_amount, unit, mint, payment_token_hash + refund_amount, unit, mint, payment_token_hash, + request_id=request_id, ) response_headers["X-Cashu"] = refund_token @@ -1923,6 +1926,7 @@ class BaseUpstreamProvider: max_cost_for_model: int, mint: str | None = None, payment_token_hash: str | None = None, + request_id: str | None = None, ) -> Response: """Handle non-streaming response for X-Cashu payment, calculating refund if needed. @@ -1995,7 +1999,8 @@ class BaseUpstreamProvider: if refund_amount > 0: refund_token = await self.send_refund( - refund_amount, unit, mint, payment_token_hash + refund_amount, unit, mint, payment_token_hash, + request_id=request_id, ) response_headers["X-Cashu"] = refund_token @@ -2032,18 +2037,17 @@ class BaseUpstreamProvider: emergency_refund = amount refund_token = await send_token(emergency_refund, unit=unit, mint_url=mint) response.headers["X-Cashu"] = refund_token - if payment_token_hash: - try: - await store_cashu_transaction( - id=payment_token_hash, - token=refund_token, - amount=emergency_refund, - unit=unit, - mint_url=mint, - type="out", - ) - except Exception: - pass + try: + await store_cashu_transaction( + token=refund_token, + amount=emergency_refund, + unit=unit, + mint_url=mint, + typ="out", + request_id=getattr(request.state, "request_id", None), + ) + except Exception: + pass logger.warning( "Emergency refund issued due to JSON parse error", @@ -2069,6 +2073,7 @@ class BaseUpstreamProvider: max_cost_for_model: int, mint: str | None = None, payment_token_hash: str | None = None, + request_id: str | None = None, ) -> StreamingResponse | Response: """Handle chat completion response for X-Cashu payment, detecting streaming vs non-streaming. @@ -2112,6 +2117,7 @@ class BaseUpstreamProvider: max_cost_for_model, mint, payment_token_hash, + request_id=request_id, ) else: return await self.handle_x_cashu_non_streaming_response( @@ -2122,6 +2128,7 @@ class BaseUpstreamProvider: max_cost_for_model, mint, payment_token_hash, + request_id=request_id, ) except Exception as e: @@ -2222,7 +2229,8 @@ class BaseUpstreamProvider: ) refund_token = await self.send_refund( - amount - 60, unit, mint, payment_token_hash + amount - 60, unit, mint, payment_token_hash, + request_id=getattr(request.state, "request_id", None), ) logger.info( @@ -2267,6 +2275,7 @@ class BaseUpstreamProvider: max_cost_for_model, mint, payment_token_hash, + request_id=getattr(request.state, "request_id", None), ) background_tasks = BackgroundTasks() background_tasks.add_task(response.aclose) @@ -2346,6 +2355,20 @@ class BaseUpstreamProvider: amount, unit, mint = await recieve_token(x_cashu_token) headers = self.prepare_headers(dict(request.headers)) + request_id = getattr(request.state, "request_id", None) + try: + await store_cashu_transaction( + token=x_cashu_token, + amount=amount, + unit=unit, + mint_url=mint, + typ="in", + request_id=request_id, + collected=True, + ) + except Exception: + pass + logger.info( "X-Cashu token redeemed for Responses API", extra={"amount": amount, "unit": unit, "path": path, "mint": mint}, @@ -2493,7 +2516,8 @@ class BaseUpstreamProvider: ) refund_token = await self.send_refund( - amount - 60, unit, mint, payment_token_hash + amount - 60, unit, mint, payment_token_hash, + request_id=getattr(request.state, "request_id", None), ) logger.info( @@ -2538,6 +2562,7 @@ class BaseUpstreamProvider: max_cost_for_model, mint, payment_token_hash, + request_id=getattr(request.state, "request_id", None), ) background_tasks = BackgroundTasks() background_tasks.add_task(response.aclose) @@ -2588,6 +2613,7 @@ class BaseUpstreamProvider: max_cost_for_model: int, mint: str | None = None, payment_token_hash: str | None = None, + request_id: str | None = None, ) -> StreamingResponse | Response: """Handle Responses API completion response for X-Cashu payment. @@ -2632,6 +2658,7 @@ class BaseUpstreamProvider: max_cost_for_model, mint, payment_token_hash, + request_id=request_id, ) else: return await self.handle_x_cashu_non_streaming_responses_response( @@ -2642,6 +2669,7 @@ class BaseUpstreamProvider: max_cost_for_model, mint, payment_token_hash, + request_id=request_id, ) except Exception as e: @@ -2669,6 +2697,7 @@ class BaseUpstreamProvider: max_cost_for_model: int, mint: str | None = None, payment_token_hash: str | None = None, + request_id: str | None = None, ) -> StreamingResponse: """Handle streaming Responses API response for X-Cashu payment. @@ -2751,7 +2780,8 @@ class BaseUpstreamProvider: ) refund_token = await self.send_refund( - refund_amount, unit, mint, payment_token_hash + refund_amount, unit, mint, payment_token_hash, + request_id=request_id, ) response_headers["X-Cashu"] = refund_token @@ -2806,6 +2836,7 @@ class BaseUpstreamProvider: max_cost_for_model: int, mint: str | None = None, payment_token_hash: str | None = None, + request_id: str | None = None, ) -> Response: """Handle non-streaming Responses API response for X-Cashu payment.""" logger.debug( @@ -2866,7 +2897,8 @@ class BaseUpstreamProvider: if refund_amount > 0: refund_token = await self.send_refund( - refund_amount, unit, mint, payment_token_hash + refund_amount, unit, mint, payment_token_hash, + request_id=request_id, ) response_headers["X-Cashu"] = refund_token @@ -2903,18 +2935,17 @@ class BaseUpstreamProvider: emergency_refund = amount refund_token = await send_token(emergency_refund, unit=unit, mint_url=mint) response.headers["X-Cashu"] = refund_token - if payment_token_hash: - try: - await store_cashu_transaction( - id=payment_token_hash, - token=refund_token, - amount=emergency_refund, - unit=unit, - mint_url=mint, - type="out", - ) - except Exception: - pass + try: + await store_cashu_transaction( + token=refund_token, + amount=emergency_refund, + unit=unit, + mint_url=mint, + typ="out", + request_id=getattr(request.state, "request_id", None), + ) + except Exception: + pass logger.warning( "Emergency refund issued for Responses API due to JSON parse error", @@ -2969,6 +3000,20 @@ class BaseUpstreamProvider: amount, unit, mint = await recieve_token(x_cashu_token) headers = self.prepare_headers(dict(request.headers)) + request_id = getattr(request.state, "request_id", None) + try: + await store_cashu_transaction( + token=x_cashu_token, + amount=amount, + unit=unit, + mint_url=mint, + typ="in", + request_id=request_id, + collected=True, + ) + except Exception: + pass + logger.info( "X-Cashu token redeemed successfully", extra={"amount": amount, "unit": unit, "path": path, "mint": mint},