Real SQLite transactions: BEGIN IMMEDIATE and synchronous=FULL (B-88, DAT-74, DAT-52, ARC-07, TX-28, BET-11, DAT-16)
pysqlite's legacy mode emitted BEGIN only at the first DML, so every read before it ran outside any transaction and a lock wait surfaced as "database is locked" at an arbitrary later statement; place_bet also mistook that error for a write-snapshot conflict, which SQLite cannot produce. - _register_sqlite_pragmas: synchronous=FULL, isolation_level=None and a `begin` event that emits BEGIN / BEGIN IMMEDIATE. - begin_immediate(session): takes the write lock before the first read; called first thing in place_bet, request_withdrawal, the scheduler transitions, _trigger_payout, the confirmation poller, the reconciler, bump_fee's persist phase, deposit crediting / external-spend marking / reinstating, and the derivation-index allocation at registration. - A lock not granted before a broadcast answers service_busy (503, all seven languages), nothing built or sent; after a broadcast nothing is undone, the rows stay `building` for the reconciler. - Spec, storico, BUGS.md and CLAUDE.md updated; 37 new tests on a real file DB. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4be58709d1
commit
7ba8e22bb4
25 files changed
+1165
-82
No files matched your search
+10
-7
@@ -205,8 +205,11 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
`app/api/errors.py:from_api_error`
|
||||
|
||||
- **[API-13]** Gli errori di `POST /bets` e `POST /withdrawals` sollevati dal
|
||||
servizio rispondono `400`, **tranne** `broadcast_failed`, che risponde `502`:
|
||||
è la rete ad aver rifiutato la transazione, non il chiamante.
|
||||
servizio rispondono `400`, **tranne** `broadcast_failed`, che risponde `502`
|
||||
(è la rete ad aver rifiutato la transazione, non il chiamante), e
|
||||
`service_busy`, che risponde `503` (il database non ha concesso il lock di
|
||||
scrittura entro il timeout, [DAT-74](modello-dati.md); nulla è stato
|
||||
costruito né trasmesso).
|
||||
`broadcast_failed` si usa **solo** per un rifiuto esplicito del server
|
||||
(un errore JSON-RPC in risposta al broadcast), e significa che tutto è stato
|
||||
annullato. Un esito **ambiguo** del broadcast (timeout, connessione caduta
|
||||
@@ -453,8 +456,8 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
`insufficient_balance` (`params.required_sats`), `too_many_inputs`
|
||||
(`params.max_inputs`), `balance_leaves_no_change`
|
||||
(`params.required_extra_sats`), `amount_below_network_fee`,
|
||||
`amount_below_dust_limit`, `502 broadcast_failed` (solo rifiuto
|
||||
esplicito). Il significato di ciascuno è nel
|
||||
`amount_below_dust_limit`, `503 service_busy`, `502 broadcast_failed`
|
||||
(solo rifiuto esplicito). Il significato di ciascuno è nel
|
||||
[catalogo](#catalogo-dei-codici-di-errore).
|
||||
- **Serializzazione:** la richiesta tiene il blocco per-utente condiviso con
|
||||
`POST /withdrawals` per tutta la costruzione e la trasmissione.
|
||||
@@ -506,8 +509,8 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
prelievo dell'intero saldo, B-96), `balance_pending_confirmation`
|
||||
(`params.pending_sats`), `insufficient_balance` (`params.required_sats`),
|
||||
`too_many_inputs` (`params.max_inputs`), `amount_below_network_fee`,
|
||||
`amount_below_dust_limit`; `502 broadcast_failed` (solo rifiuto
|
||||
esplicito).
|
||||
`amount_below_dust_limit`; `503 service_busy`; `502 broadcast_failed`
|
||||
(solo rifiuto esplicito).
|
||||
- **Serializzazione:** stesso blocco per-utente di `POST /bets`.
|
||||
- **Effetti:** segna le monete spese, crea prelievo e transazione in
|
||||
sospeso, trasmette al server attivo e poi, *best effort*, a tutti i
|
||||
@@ -1063,7 +1066,7 @@ dei parametri operativi in
|
||||
| `amount_below_minimum` | 400 | `app/withdrawals/service.py:request_withdrawal` | `minimum_sats` | prelievo sotto il costo attuale della scommessa | sì |
|
||||
| `internal_error` | 500 | `app/main.py:log_unhandled_exception` | — | eccezione non gestita | sì |
|
||||
| `withdrawal_to_platform_address` | 400 | `app/withdrawals/service.py:request_withdrawal` | — | destinazione uguale a un indirizzo di montepremi, di riserva o delle commissioni — *da implementare (B-80)* | no — da aggiungere |
|
||||
| `service_busy` | 503 | `app/bets/service.py:place_bet` | — | il database non ha concesso il lock di scrittura entro il timeout; nulla è stato trasmesso, si può riprovare — *da implementare (B-88)* | no — da aggiungere |
|
||||
| `service_busy` | 503 | `app/bets/service.py:place_bet`, `app/withdrawals/service.py:request_withdrawal`, `app/auth/routes.py:register` | — | il database non ha concesso il lock di scrittura entro il timeout; nulla è stato trasmesso, si può riprovare | sì |
|
||||
| `registration_unavailable` | 503 | `app/auth/routes.py:register` | — | registrazione sospesa finché il controllo degli indici di derivazione dopo un ripristino non è concluso o risolto ([registrazione-e-depositi.md](registrazione-e-depositi.md), B-90) — *da implementare* | no — da aggiungere |
|
||||
| `not_authenticated` | 401 | dipendenza Bearer di FastAPI | — | header `Authorization` assente — oggi `detail` stringa — *da implementare (B-94)* | no — da aggiungere |
|
||||
| `invalid_admin_token` | 403 | `app/api/routes/admin.py:require_admin` | — | token admin assente o errato — oggi `detail` stringa — *da implementare (B-94)* | no — da aggiungere |
|
||||
|
||||
Reference in new issue
Block a user