fix(mint): close websocket when subscribed mint quotes expire unpaid#1036
fix(mint): close websocket when subscribed mint quotes expire unpaid#1036b-l-u-e wants to merge 1 commit into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1036 +/- ##
==========================================
+ Coverage 75.04% 75.17% +0.13%
==========================================
Files 111 110 -1
Lines 12244 12128 -116
==========================================
- Hits 9188 9117 -71
+ Misses 3056 3011 -45 ☔ View full report in Codecov by Sentry. |
Persist mint quote expiry in the database (migration + store path) and poll bolt11_mint_quote subscriptions so idle websocket connections are closed after all subscribed quotes are unpaid and past expiry. Fixes MintQuote.from_row not loading expiry and store_mint_quote not writing it (melt quotes already had expiry; mint quotes did not).
118a270 to
8b9af9c
Compare
| if ( | ||
| not mint_quote | ||
| or not mint_quote.unpaid | ||
| or not mint_quote.expiry | ||
| or mint_quote.expiry > now | ||
| ): | ||
| all_expired = False | ||
| break |
There was a problem hiding this comment.
Correct me if I'm wrong, looking at the condition — a paid quote causes not mint_quote.unpaid to be true, which sets all_expired = False and breaks. So a paid quote actually keeps the connection alive. If a wallet doesn't close the connection after payment (even though nutshell's wallet does), this task won't clean it up either.
Would it make sense to treat paid quotes as terminal too, so the check becomes "close if every subscribed quote is either paid or expired+unpaid"?
There was a problem hiding this comment.
I think that makes sense.
afaik we don't respect the expired field for mint quotes in the mint, so I'm not sure if we should drop the connection
| # already paid (even if expiry is in the past) -> keep open | ||
| (MintQuoteState.paid, lambda: int(time.time()) - 10), |
There was a problem hiding this comment.
Referring to earlier suggestion about Paid being a terminal state so perhaps it should also be closed.
Inspired by the quote lifecycle work in #1022 (wallet-side handling of unpaid/expired invoices), this PR hardens the mint side: websocket subscriptions to
bolt11_mint_quoteare closed when all subscribed quotes are unpaid and pastexpiry, instead of holding connections open untilmint_websocket_read_timeout(default 600s).Also fixes mint quote
expirynot being persisted or returned on GET: POST returnedexpiry, but GET and the websocket monitor sawnullbecausemint_quoteshad noexpirycolumn andstore_mint_quotenever wrote it.Problem
expirywas computed at quote creation but not stored inmint_quotes(unlikemelt_quotes), so REST GET,MintQuote.from_row, and the expiry monitor could not see it.Changes
MintQuote.from_row: loadexpiryfrom DB rows (SQLite + Postgres).store_mint_quote: persistexpiryon insert.m036_add_expiry_to_mint_quotes: addexpirycolumn tomint_quotes.LedgerEventClientManager: background poll (MINT_WEBSOCKET_QUOTE_EXPIRY_CHECK_INTERVAL, default 30s) closes WS with code1000/ reasonmint quote subscription expiredwhen every subscribed mint quote is unpaid andexpiry <= now.get_mint_quote.Verification
Local mint:
POST and GET both return matching expiry:
{ "quote": "k4D70Q30zKmxgnAGmxGcGLYSdRNKL71TXj8ZRttb", "state": "UNPAID", "expiry": 1780484602 }WebSocket (python3 /tmp/ws_quote_hold.py "$QUOTE_ID"), ~60s after quote creation:
Mint log:
After expiry, GET still returns state: "UNPAID" with expiry set expected: state tracks payment/mint progress; expiry is a separate deadline. Clients treat UNPAID + expiry < now as dead; POST /v1/mint/bolt11 rejects with quote expired.
Test plan