Complete the target spec for the 2026-09-28 review (B-73..B-98)

Finishes what 88221b0 started: sicurezza.md, audit-log.md and storico-bug.md
now describe the target behaviour too, the operator guides are corrected
against today's code, and api.md gains the missing /health/ready and
/admin/electrum sections plus every new error code in its catalog.

Harmonised names and owners across documents (the `resurrected_tx` pause
reason, the reconciler as the one that watches failed rows, the full
`winner_drawn` payload, a reason for an unanswered pre-payout pool check,
one corroboration-failure row per hash instead of one per tick).

Adds B-98 (coin selection stops at the first covering UTXO and refuses a
payable bet), tightens RND-40 to check both 1 and 400 participants and to
guard PUT /admin/config, and gives a stalled `closing` round a signal
(TX-29, closing_stalled).

Records three decisions confirmed on 2026-09-29: the draw's server_seed is
stored Fernet-encrypted until revealed (SEC-56, DAT-64), the chosen abuse
and key-check thresholds (SEC-49, SEC-54, SEC-55), and a broadcast
rejection is verified against every server before any coin is released,
so matching "already known" text is a shortcut, not a safety condition
(TX-04).

Spec only; no code changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Opus 5.5 committed 2026-09-29 09:17:52 +02:00
1 parent 88221b0dbd
commit 4be58709d1
16 files changed
+1377 -323

No files matched your search

+89 -33
View File
@@ -26,7 +26,15 @@ sola schermata di login.
- **Round** — storico round: stato, vincitore, importi, txid di payout
- **Transazioni pendenti** — bet/payout/prelievi non ancora confermati, candidati al fee-bump RBF
- **Audit log** — eventi registrati dal sistema (config cambiata, bet, payout, accessi a chiavi private, ecc.; catalogo completo in [audit-log.md](audit-log.md))
- **Bug report** — segnalazioni inviate da `/report-bug`, da smistare `open` → `read` → `resolved`
- **Segnalazioni bug** — segnalazioni inviate da `/report-bug`, da smistare `open` → `read` → `resolved`
Nella navbar, a destra, una pillola mostra lo stato del round ("In attesa
del prossimo round", "Round aperto", oppure "Estrazione in corso", che copre
chiusura, estrazione e pagamento) e l'altezza dell'ultimo blocco noto,
aggiornate ogni 15 secondi da `GET /rounds/current`. Non indica se la
connessione Electrum è attiva né a quale server: un'altezza che non sale per
parecchi minuti è il segnale da guardare (procedura in
[operativita.md](operativita.md), "Electrum irraggiungibile").
## Parametri
@@ -39,21 +47,24 @@ business — quelli si toccano solo da qui.
| Campo | Significato |
|---|---|
| **Fee address** | L'indirizzo PLM su cui finisce il 30% di ogni round (fee). Obbligatorio: finché è vuoto **non si apre nessun round** (il payout non sarebbe costruibile, quindi il round accetterebbe scommesse per poi restare bloccato con i soldi già nel montepremi). Il pannello lo segnala con un avviso in cima ai Parametri, e la pagina utente mostra un banner "lotteria non ancora pronta". Un round già in corso non viene interrotto se svuoti il campo: chiude, estrae e paga normalmente. |
| **Fee address** | L'indirizzo PLM su cui finisce il 30% di ogni round (fee). Obbligatorio: su un'istanza nuova è vuoto, e finché resta vuoto **non si apre nessun round** (il payout non sarebbe costruibile, quindi il round accetterebbe scommesse per poi restare bloccato con i soldi già nel montepremi). Il pannello lo segnala con un avviso in cima ai Parametri, e la pagina utente mostra un banner "lotteria non ancora pronta". Una volta impostato non si può più svuotare dal pannello: un valore vuoto non è un indirizzo `plm1...` valido e viene rifiutato. **Attenzione:** oggi il payout legge questo campo al momento in cui viene costruito, quindi cambiarlo mentre un round è in corso sposta anche la commissione di quel round sul nuovo indirizzo. |
| **Bet amount (PLM)** | Il costo fisso d'ingresso per round. È anche l'importo minimo prelevabile: un prelievo sotto questa soglia viene rifiutato (i depositi non hanno un controllo minimo lato server). |
| **Durata round (secondi)** | Quanto resta aperto un round prima di chiudersi ed estrarre il vincitore. Il taglio per le nuove giocate scatta esattamente allo scadere di questo tempo (verificato ad ogni bet, non dipende dal ciclo dello scheduler) — è un "semaforo giallo": nessuna nuova entrata, ma le bet già trasmesse prima dello scadere hanno comunque tempo di confermarsi prima che il round chiuda ed estragga. |
| **Pausa tra un round e il successivo (secondi)** | Cooldown dopo la chiusura di un round, prima che il successivo si apra — dà tempo ai giocatori di vedere l'esito. |
| **Durata animazione estrazione (secondi)** | Tempo minimo per cui la dashboard di ogni utente mostra l'animazione "Estrazione in corso" dopo la chiusura del round, prima di rivelare il vincitore. È solo un minimo: il processo reale aspetta fino a 3 blocchi in sequenza (conferma dell'ultima bet in sospeso, blocco dell'estrazione, conferma del payout — un blocco ogni 2 minuti in media), quindi l'animazione può durare più a lungo di questo valore, mai meno. |
| **Fee rate di rete (sat/vB)** | Fee per byte usata per costruire bet, payout e prelievi. |
| **Timeout prima del fee-bump RBF (secondi)** | Dopo quanto tempo senza conferma una transazione viene ritrasmessa con fee più alta. |
Durata e cooldown si applicano **dal round successivo**, non a quello già in
corso: ogni round si porta dietro i valori con cui è stato aperto, così
abbassare la durata mentre un round è a metà non lo chiude di colpo, e alzarla
non sposta il countdown che i giocatori stanno già guardando. Gli altri
parametri (bet amount, fee rate, RBF timeout) restano invece a effetto
immediato.
| **Durata animazione estrazione (secondi)** | Tempo minimo per cui la dashboard di ogni utente mostra l'animazione "Estrazione in corso" dopo la chiusura del round, prima di rivelare il vincitore. È solo un minimo: il processo reale aspetta fino a 3 blocchi confermati in sequenza (ultima bet in sospeso, estrazione, payout — ~2 minuti l'uno), quindi l'animazione può durare più a lungo di questo valore, mai meno. |
| **Fee rate di rete (sat/vB)** | Fee per byte usata per costruire bet, payout e prelievi. |
| **Timeout prima del fee-bump RBF (secondi)** | Dopo quanto tempo senza conferma una transazione viene ritrasmessa con fee più alta. |
non sposta il countdown che i giocatori stanno già guardando. Tutti gli altri
parametri sono invece a effetto immediato, anche sul round in corso: un nuovo
bet amount vale per le scommesse successive (il montepremi annunciato resta
calcolato su quanto è stato davvero puntato), fee rate e RBF timeout valgono
per le prossime transazioni costruite o accelerate, compreso il payout del
round in corso, e il fee address vale per il prossimo payout costruito (vedi
sopra).
Tutti gli importi in PLM vengono convertiti in sats (1 PLM = 100.000.000 sats)
solo nella chiamata API — il backend lavora sempre in sats.
@@ -74,30 +85,42 @@ sorprenderli con un'interruzione improvvisa.
- **"Interrompi dopo questo round"**: il round eventualmente in corso viene
**completato normalmente** — chiude, estrae il vincitore da un blocco
confermato, e paga il 70/30 come sempre. Solo l'apertura del **round
successivo** viene sospesa. Gli utenti vedono un avviso di manutenzione
sulla loro dashboard (e sulla home, anche da sloggati) finché la lotteria
resta in pausa.
successivo** viene sospesa. La pausa non ferma nemmeno le scommesse e i
prelievi sul round ancora aperto: il round prosegue fino alla sua scadenza.
Gli utenti vedono un avviso di manutenzione sulla loro dashboard (e sulla
home, anche da sloggati) finché la lotteria resta in pausa.
- **"Riprendi lotteria"**: annulla la pausa — al prossimo giro dello
scheduler (ogni 5 secondi) un nuovo round si apre normalmente (rispettando
comunque il cooldown se il precedente si è appena chiuso).
Ogni pausa/ripresa viene registrata nell'audit log (`lottery_paused` /
`lottery_resumed`), ma — come per il resto del pannello — non registra
*quale* operatore l'ha premuta (token condiviso, vedi limiti noti in
[CLAUDE.md](../CLAUDE.md)).
*quale* operatore l'ha premuta (token condiviso, vedi
[decisioni-e-limiti.md](decisioni-e-limiti.md)).
Prima di un riavvio o di un deploy la pausa da sola non basta: bisogna anche
aspettare che il round in corso sia `closed`, perché oggi un riavvio durante
l'estrazione blocca il round. La procedura è in
[running-the-server.md](running-the-server.md) e, completa, in
[operativita.md](operativita.md) (OPS-10).
**Chi paga il fee-bump RBF?** Quando una bet, un payout o un prelievo resta
troppo a lungo senza conferma (oltre il "Timeout prima del fee-bump RBF"), il
sistema lo ritrasmette con una fee più alta. Il costo aggiuntivo lo assorbe
sempre **chi ha originato la transazione**, non il destinatario: per bet e
prelievi è l'utente stesso (gli torna un resto più piccolo), per i payout è
il pool (il resto che torna all'indirizzo pool si riduce) — la quota del
il pool (si riduce il resto che torna all'indirizzo pool) — la quota del
vincitore e quella delle fee, già fissate, non vengono mai toccate. Se non
c'è un resto abbastanza grande da assorbire l'aumento, il bump fallisce: la
transazione resta in attesa finché non conferma da sola o finché il
riconciliatore non la abbandona liberandone le monete (vedi
[transazioni-e-riconciliazione.md](transazioni-e-riconciliazione.md) e
[operativita.md](operativita.md)).
c'è un resto abbastanza grande da assorbire l'aumento, il bump fallisce (nel
log `could not bump pending_transaction N`, ripetuto ogni 30 secondi) e la
transazione resta in attesa. **Per i payout è il caso normale**: il pool di
un round contiene di solito esattamente le scommesse del round e il payout le
spende tutte, quindi non ha resto e non si può accelerare. Una transazione
ferma nel mempool non viene nemmeno abbandonata, perché il server la conosce:
se è un payout, il round resta in `paying_out` e nessun nuovo round si apre
finché non conferma. Cosa fare è in [operativita.md](operativita.md);
il funzionamento in
[transazioni-e-riconciliazione.md](transazioni-e-riconciliazione.md).
## Round
@@ -108,27 +131,36 @@ txid.
## Transazioni pendenti
`GET /admin/pending-transactions` elenca bet, payout e prelievi ancora senza
conferma: tipo, stato, txid corrente, fee rate usata, numero di tentativi
(si incrementa a ogni bump RBF) e orario dell'ultima trasmissione. Una riga
che resta qui a lungo, con `attempt_count` che sale, indica una transazione
in difficoltà — vedi "Limiti noti" sul fallback RBF.
`GET /admin/pending-transactions` elenca le ultime 50 transazioni tracciate
(bet, payout e prelievi), dalla più recente, **in qualunque stato**: non solo
quelle ancora `building` o `pending`, ma anche quelle già `confirmed` o
`failed` — per sapere cosa è davvero in sospeso si guarda la colonna Stato.
Per ogni riga: id, tipo, stato, txid corrente, fee rate usata, numero di
tentativi (si incrementa a ogni bump RBF) e orario del **primo** invio (non
cambia con i bump). Una riga che resta `pending` a lungo, con il numero di
tentativi che sale, indica una transazione in difficoltà; una che resta
`pending` con il numero di tentativi fermo è spesso un bump impossibile (vedi
sopra "Chi paga il fee-bump RBF?").
## Audit log
`GET /admin/audit-log` elenca gli ultimi 200 eventi registrati dal sistema
(tipo evento, payload JSON, utente/round coinvolti, timestamp): bet
piazzate, payout inviati, round chiusi, configurazione modificata, accessi
alle chiavi private, ecc. È il primo posto da controllare per ricostruire
cosa è successo dopo un problema.
piazzate, payout inviati, round chiusi senza partecipanti, configurazione
modificata, accessi alle chiavi private, ecc. È il primo posto da controllare
per ricostruire cosa è successo dopo un problema. La sezione non ha filtri né
ricerca, e l'endpoint accetta solo `limit` (al massimo 500): gli eventi più
vecchi si leggono solo direttamente dal database. Non tutto viene
registrato: per esempio registrazioni, cambi password fatti dagli utenti e
bump RBF non lasciano righe (elenco in [audit-log.md](audit-log.md)).
Eventi a cui vale la pena prestare attenzione:
| Evento | Significato |
|---|---|
| `config_updated` | Un parametro è stato modificato; il payload contiene valore precedente e nuovo per ogni campo cambiato. |
| `bet_broadcast_failed` / `withdrawal_broadcast_failed` | La rete ha rifiutato la transazione. Non è stato speso nulla: gli UTXO sono stati liberati e il saldo dell'utente è tornato come prima. |
| `pending_tx_abandoned` | Una transazione trasmessa è scomparsa dalla catena e il sistema l'ha dichiarata persa: UTXO liberati, bet rimossa o prelievo segnato `failed`. Se capita spesso, la fee rate configurata è probabilmente troppo bassa. |
| `bet_broadcast_failed` / `withdrawal_broadcast_failed` | L'invio della transazione non è riuscito: gli UTXO sono stati liberati e il saldo dell'utente è tornato come prima. Oggi l'evento viene scritto per **qualunque** errore d'invio, anche un timeout o una connessione caduta dopo l'invio, casi in cui la transazione potrebbe essere partita lo stesso: se il `reason` del payload non è un rifiuto esplicito della rete, controllare il `txid` con un explorer. |
| `pending_tx_abandoned` | Il sistema ha dichiarato persa una transazione: UTXO liberati, bet rimossa o prelievo segnato `failed`. Oggi basta che il server Electrum attivo non la conosca (dopo 120 secondi per una riga `building`, 6 ore per una `pending`): dopo un riavvio o un cambio di server può essere un falso allarme, con la transazione ancora viva o già confermata. Controllare sempre il txid del payload con un explorer — procedura in [operativita.md](operativita.md) ("Transazione abbandonata"). |
| `pending_tx_recovered` | Una transazione che si credeva incompleta è invece finita in catena (tipicamente dopo un riavvio a metà invio) e il sistema l'ha ripresa da sé. |
| `bug_report_status_changed` | Un admin ha cambiato lo stato di una segnalazione (payload: `report_id`, stato precedente e nuovo). Con un token admin unico e condiviso, questa riga è l'unica traccia di chi tocca le segnalazioni. |
| `payout_failed` | Un tentativo di payout di un round non è partito; il campo `reason` dice perché. Il round resta in `paying_out` e lo scheduler **riprova da solo al massimo una volta ogni 60 secondi**. Se l'evento si ripete, controlla `fee_address`, il saldo dell'indirizzo pool e la connessione Electrum (procedure in [operativita.md](operativita.md)). |
@@ -160,9 +192,6 @@ un'altra catena (per esempio `bc1...`) sarebbe formalmente valido come witness
program, e il 30% di commissione di ogni round finirebbe su uno script di cui
nessuno ha la chiave.
```bash
```
## Utenti, chiave privata e reset password
La card "Utenti" elenca id, username, indirizzo e saldo di ogni utente
@@ -195,7 +224,34 @@ per l'utente: solo un admin col token può farlo.
round resta in `paying_out` e il payout viene ritentato ogni 60 secondi;
alcuni motivi però non si risolvono da soli (vedi
[operativita.md](operativita.md)).
- Un riavvio mentre un round è in estrazione (`drawing`) lo blocca: prima
di ogni riavvio mettere in pausa e aspettare `closed` (vedi
"Manutenzione" sopra).
- Elenco completo dei limiti accettati: [decisioni-e-limiti.md](decisioni-e-limiti.md).
Per l'elenco completo dei gap noti vedi la sezione "Known gaps / TODO" in
`CLAUDE.md`.
## Cosa cambierà
La revisione del 2026-09-28 ha deciso delle correzioni non ancora
implementate; quelle che cambiano ciò che si vede da questo pannello sono
(dettaglio e ordine in [piano-correzioni.md](piano-correzioni.md)):
- **La pausa avrà un motivo.** Oltre a quella manuale, il sistema metterà in
pausa la lotteria da solo per una riorganizzazione profonda della catena,
per un payout bloccato o per una transazione data per persa e poi
confermata; il pannello mostrerà il motivo, e "Riprendi lotteria" resterà
l'unico modo di uscirne, dopo le verifiche di
[operativita.md](operativita.md) (OPS-30).
- **Payout bloccato.** Se la scommessa del vincitore sparisce dalla catena,
il payout non partirà (evento `payout_blocked`) e il round resterà in
`paying_out` fino alla ripresa; i partecipanti la cui scommessa è sparita
verranno esclusi dal montepremi (`participant_reorged_out`).
- **Stato Electrum.** Il pannello mostrerà se la connessione è attiva, il
server in uso e da quanto non arriva un blocco.
- **Audit log consultabile per intero**, con filtri per tipo di evento,
round, utente e intervallo di tempo, e a pagine; nuovi eventi come
`user_registered`, `password_changed`, `rbf_bump_failed`,
`closing_stalled` e `chain_reorg_detected` (catalogo in
[audit-log.md](audit-log.md)).