2026-10-14 12:05:57 +02:00
# Limiti noti
Questo documento raccoglie i **limiti noti (`LIM`)** : difetti di copertura o
di robustezza accettati consapevolmente, con l'impatto, ciò che oggi li
attenua e cosa servirebbe per chiuderli. Le **decisioni di progetto** non sono
più qui: ognuna è un ADR in [adr/ ](adr/README.md ).
Un limite noto **non è un bug** : un bug è un comportamento che contraddice la
specifica e prende un numero `B-nn` ([storico-bug.md ](storico-bug.md )). Se si
decide di chiudere un limite, si aggiorna prima la voce `LIM` qui (o la si
barra come rimossa) e poi il requisito del documento di dominio, secondo
[processo-di-modifica.md ](processo-di-modifica.md ).
Un limite che il [piano delle correzioni ](piano-correzioni.md ) chiuderà porta
`*Stato:* da chiudere con B-nn` , e *Per chiuderlo* descrive il comportamento
atteso; quando la correzione entra nel codice, il limite si barra come
`~~[LIM-nn]~~ Rimosso: chiuso da B-nn` .
Formato di ogni voce: descrizione, *Impatto* , *Mitigazione attuale* , *Per
chiuderlo*, e il codice coinvolto.
### Quali limiti bloccano il funzionamento non presidiato
La revisione del 2026-09-28 ha mostrato che la tabella precedente era
incompleta: oltre a `drawing` e all'RBF, bloccavano il funzionamento non
presidiato le falle critiche B-73 … B-82 (estrazione manipolabile, risposte
oltre 64 KiB, abbandoni su un solo server, pagamento senza riserva, deposito
spendibile due volte). La colonna *Dopo il piano* dice cosa resta una volta
implementato il [piano delle correzioni ](piano-correzioni.md ).
| Limite | Blocca il non presidiato oggi? | Dopo il piano |
|---|---|---|
2026-10-16 13:34:43 +02:00
| ~~[LIM-01]~~ `drawing` non riprende dopo un errore o un riavvio | Non più (chiuso da B-81) | — |
2026-10-16 14:53:10 +02:00
| [LIM-02] RBF di una sola forma | Non più il pagamento senza resto (B-79) né il resto sotto soglia (B-87); resta una transazione che esaurisce resto o riserva | Visibile in `rbf_bump_failed` ; resta in attesa senza abbandono |
2026-10-14 12:05:57 +02:00
| [LIM-03] Attesa in `closing` senza limite di tempo | **Sì, in caso di congestione** | Resta senza limite di tempo per scelta ([ADR-51 ](adr/ADR-51-dubbio-liberano-mai-fondi.md )); con B-78 e B-87 finisce quando la rete decide |
| [LIM-04] Prelievo e RBF mai provati in rete reale | **Sì, come verifica** | Da chiudere con la prova su rete reale ([operativita.md ](operativita.md#prova-su-rete-reale )) prima di aprire al pubblico |
Credit deposits at deposit_confirmations and verify the round's coins with the quorum before paying (B-82, DEP-03, DEP-04, DEP-09, DEP-12, DEP-14, ELC-17, ELC-27, RND-21, RND-22, RND-25, RND-39, DAT-10, DAT-11, DAT-24, DAT-62, DAT-67, DAT-68, AUD-12, AUD-44, AUD-45, API-30, API-31, API-36, ADR-06, ADR-52)
2026-10-16 14:25:35 +02:00
| ~~[LIM-10]~~ Riorganizzazioni non gestite | Non più (chiuso da B-82 e B-85) | Resta [LIM-24] |
| [LIM-11], ~~[LIM-12]~~ Quorum facoltativo e parziale | Non più (chiusi da B-82, B-83 e B-84) | Resta la scelta dell'operatore di server indipendenti ([LIM-11]) |
2026-10-14 12:05:57 +02:00
| [LIM-13] Estrazione manipolabile | **Sì** — per un giocatore che mina | Chiuso da B-73; resta [LIM-25] |
| [LIM-14] Header senza difficoltà minima | **Sì** — un header falso congela ogni estrazione | Chiuso da B-74 per il blocco; resta il costo basso di un header a difficoltà minima |
| [LIM-06] Token admin unico | No per il funzionamento, ma è il rischio di sicurezza più ampio | Invariato |
| tutti gli altri | No | — |
### Robustezza del ciclo di vita
2026-10-16 13:34:43 +02:00
- ~~[LIM-01]~~ Rimosso: chiuso da B-81. Un round in `drawing` riprende da solo
dopo un errore o un riavvio, con lo stesso blocco, gli stessi partecipanti e lo
stesso vincitore ([RND-36 ](ciclo-di-vita-del-round.md )).
2026-10-14 12:05:57 +02:00
- **[LIM-02]** **L'RBF gestisce una sola forma di transazione** : un'unica uscita
2026-10-16 14:53:10 +02:00
riducibile — il resto verso il mittente per scommesse e prelievi, l'uscita
di riserva per il pagamento ([RND-38 ](ciclo-di-vita-del-round.md#4--pagamento ), B-79).
Non c'è il ripiego «aggiungi un'altra moneta» né il CPFP. Ogni aumento costa
almeno 1 sat/vB sull'intera dimensione della transazione (141 sat per una
transazione con un ingresso e due uscite) e si toglie da quell'uscita; se
quanto resta sarebbe sotto la soglia di polvere l'uscita viene eliminata e
il suo valore va in commissione, e quella sostituzione è l'ultima (B-87,
[ADR-19 ](adr/ADR-19-ogni-transazione-utente-conserva-uscita-resto.md )); se
nemmeno l'intera uscita raggiunge l'aumento minimo, o non esiste, `bump_fee`
solleva `RbfError` .
- *Impatto:* con la rete congestionata una transazione a 1 sat/vB che ha
esaurito resto o riserva può restare in mempool indefinitamente. Poiché il
riconciliatore abbandona solo ciò che il server **non conosce** , una
transazione ferma ma presente in mempool non viene mai abbandonata: una
scommessa così blocca il round in `closing` , un pagamento così lo blocca
in `paying_out` .
- *Mitigazione attuale:* la riserva del pagamento è dimensionata per circa
10 sat/vB; resto minimo garantito sulle transazioni utente; tetto al fee
rate (`MAX_FEE_RATE_SAT_VB` ); ogni accelerazione impossibile o rifiutata
scrive `rbf_bump_failed` (una volta per transazione e motivo); abbandono
dopo 6 ore per ciò che sparisce dalla rete; ritentativo automatico del
pagamento dopo un abbandono.
- *Stato:* accettato dopo B-79 e B-87: una transazione che ha esaurito resto
o riserva e non conferma nemmeno al tetto del fee rate resta in attesa,
visibile, e non viene abbandonata ([ADR-51 ](adr/ADR-51-dubbio-liberano-mai-fondi.md )).
2026-10-14 12:05:57 +02:00
*Codice:* `app/tx/broadcast.py:bump_fee` · `app/wallet/psbt_builder.py:build_payout_transaction` · `app/tx/reconcile.py:reconcile_once`
- **[LIM-03]** **L'attesa in `closing` non ha un limite proprio.** Il round
Abandon a transaction only on proof, and keep watching failed rows (B-78, TX-15, TX-17, TX-23, TX-26, ELC-28, DAT-33, DAT-36, DAT-69, DAT-72, AUD-11, AUD-12, AUD-31, AUD-39, API-32)
2026-10-14 15:07:09 +02:00
aspetta ogni scommessa `building` /`broadcast` . A porre fine all'attesa di
una scommessa che non conferma è la sua conferma (una qualsiasi delle sue
versioni) oppure il riconciliatore, ma solo con la prova che non è partita
([ADR-51 ](adr/ADR-51-dubbio-liberano-mai-fondi.md )); una scommessa ferma ma presente in mempool trattiene il round senza
limite ([LIM-02]).
2026-10-14 12:05:57 +02:00
- *Impatto:* un round può restare chiuso ma non estratto per ore; nessun
nuovo round si apre nel frattempo.
- *Mitigazione attuale:* lo stato è visibile (`closing` ) e il pannello
«Transazioni pendenti» mostra la scommessa che trattiene il round.
2026-10-16 14:53:10 +02:00
- *Stato:* B-78, B-77 e B-87 chiusi; resta per scelta l'assenza di un limite di tempo.
2026-10-14 12:05:57 +02:00
- *Per chiuderlo:* con B-78 l'attesa finisce quando la scommessa conferma
(una qualsiasi delle sue versioni, B-77) o quando c'è la prova che non
confermerà mai ([ADR-51 ](adr/ADR-51-dubbio-liberano-mai-fondi.md )), non dopo un tempo fisso; con B-87 la scommessa
resta accelerabile fino all'eliminazione del resto. Un **limite di tempo
resta escluso per scelta**: escludere e rimborsare una scommessa lenta
libererebbe fondi senza prova, che è ciò che [ADR-51 ](adr/ADR-51-dubbio-liberano-mai-fondi.md ) vieta. Resta quindi
accettato che una scommessa ferma al tetto del fee rate trattenga il
round.
*Codice:* `app/rounds/scheduler.py:RoundScheduler._tick` · `app/tx/reconcile.py:_is_due`
- **[LIM-04]** **Prelievo e accelerazione RBF non sono mai stati inviati in una
rete reale.** Sono coperti da test unitari, rami di errore e annullamento
compresi; il resto del flusso (registrazione, deposito, scommessa, un ciclo
completo di round con pagamento) è stato verificato in mainnet.
- *Impatto:* un errore di costruzione o di firma specifico della rete reale
emergerebbe al primo prelievo di un utente vero.
- *Stato:* da chiudere con la fase 7 del [piano ](piano-correzioni.md#fase-7--prova-su-rete-reale ).
- *Per chiuderlo:* un prelievo e un RBF reali in mainnet, con verifica
dell'aritmetica sulla transazione inviata, dentro la prova con importi
piccoli e guasti provocati di
[operativita.md ](operativita.md#prova-su-rete-reale ).
*Codice:* `app/withdrawals/service.py:request_withdrawal` · `app/tx/broadcast.py:bump_fee`
- **[LIM-05]** **Nessun test di integrazione con una connessione Electrum
vera.** `tests/integration/` è vuota; la verifica dal vivo è stata tutta
manuale (`scripts/electrum_smoke_test.py` , script ad hoc, transazioni reali).
- *Impatto:* le differenze di comportamento fra implementazioni di server
Electrum (formati di risposta, errori) emergono solo in produzione.
- *Per chiuderlo:* lo strato di test di guasto e di integrazione previsto in
[architettura.md ](architettura.md#test ) (server Electrum simulati che
rispondono in ritardo, tagliano la connessione, mandano risposte enormi o
catene divergenti), più la prova su rete reale di
[operativita.md ](operativita.md#prova-su-rete-reale ). Nessuna delle falle
critiche del 2026-09-28 sarebbe stata trovata dai test attuali.
*Codice:* `tests/integration/__init__.py` · `scripts/electrum_smoke_test.py`
### Sicurezza e amministrazione
- **[LIM-06]** **Un unico token admin condiviso, senza identità per
operatore.** Il registro di audit dice *cosa* è cambiato (per esempio
`config_updated` con prima/dopo) ma non *chi* . Il token dà accesso a elenco
utenti, esportazione delle chiavi private, reset delle password e storico.
Non c'è limite ai tentativi sul token.
- *Impatto:* una fuga del token ha il raggio d'azione più ampio del sistema,
ed è impossibile attribuire un'azione a una persona.
- *Mitigazione attuale:* confronto a tempo costante, pannello chiuso se il
token è vuoto, ogni azione sensibile in audit, `/admin` non collegato dalla
pagina pubblica. Dettagli in [sicurezza.md ](sicurezza.md ).
- *Per chiuderlo:* account amministrativi individuali (con l'identità scritta
in ogni voce di audit) e limitazione dei tentativi sul token.
*Codice:* `app/api/routes/admin.py:require_admin` · `app/audit/log.py:write_audit_log`
- **[LIM-07]** **Nessuna limitazione di frequenza su scommesse, prelievi,
endpoint admin, cambio password e segnalazioni di bug.** Solo login e
registrazione sono limitati ([ADR-41 ](adr/ADR-41-limitazione-tentativi-su-login-registrazione.md )); il canale SSE ha tetti di
concorrenza (500 globali, 5 per IP), non di frequenza.
- *Impatto:* un utente autenticato può martellare scommesse e prelievi (il
blocco per-utente li serializza, non li limita); il cambio password
permette di provare password attuali senza ritardi a chi possiede già un
token valido; le segnalazioni anonime possono riempire il database.
- *Stato:* da chiudere in parte con B-94 (cambio password limitato e
registrato, segnalazioni di bug limitate per IP, lunghezza massima di 256
caratteri sui campi delle credenziali). Scommesse, prelievi ed endpoint
admin restano senza limite di frequenza anche dopo il piano.
- *Per chiuderlo:* un limitatore per utente e per IP sugli endpoint
interessati, sullo stesso modello di `RateLimiter` /`RollingQuota` .
*Codice:* `app/api/routes/bets.py:create_bet` · `app/api/routes/withdrawals.py:create_withdrawal` · `app/api/routes/users.py:change_password` · `app/api/routes/bug_reports.py:create_bug_report`
- **[LIM-08]** **Nessun registro di sicurezza lato utente.** Registrazione,
login, login falliti e cambio password dell'utente non scrivono voci di audit
(le scrive solo il reset da parte dell'admin).
- *Impatto:* dopo una compromissione non c'è traccia di quando e da dove sia
avvenuto l'accesso.
- *Stato:* da chiudere in parte con B-92 (`user_registered` , con indice di
derivazione e indirizzo) e B-94 (`password_changed` ). Login riusciti e
falliti restano senza voce di audit dopo il piano.
- *Per chiuderlo:* eventi di audit per questi casi (vedi
[audit-log.md ](audit-log.md )).
*Codice:* `app/auth/routes.py:login` · `app/api/routes/users.py:change_password`
- **[LIM-09]** **Il JWT sta in `localStorage` e la CSP ammette script inline.**
Una falla XSS permetterebbe di leggere il token.
- *Mitigazione attuale:* CSP `default-src 'self'` , `frame-ancestors 'none'` ,
escape dei valori controllati dall'utente nelle pagine; sessioni revocabili
([ADR-36 ](adr/ADR-36-password-argon2-sessioni-jwt-hs256-24.md )).
- *Per chiuderlo:* togliere gestori e stili inline dalle SPA per poter
eliminare `'unsafe-inline'` , oppure spostare la sessione in un cookie
`HttpOnly` .
*Codice:* `Caddyfile` · `app/static/app.js`
- **[LIM-26]** **La piattaforma è custodiale: gli utenti devono fidarsi
dell'operatore.** Tutte le chiavi stanno sul server ([ADR-01 ](adr/ADR-01-piattaforma-custodiale-tutte-chiavi-generate-tenute.md )): l'operatore
(o chi compromette il server) può spendere ogni fondo, leggere
`server_seed` prima della rivelazione e scegliere il vincitore con un
minatore ([LIM-25]). Nessuna misura del piano lo cambia.
- *Impatto:* il rischio di perdita totale dei fondi depositati dipende dalla
sicurezza del server e dall'onestà dell'operatore.
- *Mitigazione dopo il piano:* chiave cifrata a riposo con la chiave di
cifratura in un file separato (B-91), container non root, verifica della
chiave all'avvio (B-89), prova pubblica di ogni estrazione ([ADR-24 ](adr/ADR-24-l-estrazione-commit-reveal-fra-server.md )), audit
di ogni azione sensibile. Modello di minaccia in
[sicurezza.md ](sicurezza.md#modello-di-fiducia ).
- *Per chiuderlo:* un disegno non custodiale, scartato in [ADR-01 ](adr/ADR-01-piattaforma-custodiale-tutte-chiavi-generate-tenute.md ).
*Codice:* `app/wallet/hd.py:_load_account_key` · `app/wallet/keystore.py:decrypt_xprv`
### Fiducia nella rete
Credit deposits at deposit_confirmations and verify the round's coins with the quorum before paying (B-82, DEP-03, DEP-04, DEP-09, DEP-12, DEP-14, ELC-17, ELC-27, RND-21, RND-22, RND-25, RND-39, DAT-10, DAT-11, DAT-24, DAT-62, DAT-67, DAT-68, AUD-12, AUD-44, AUD-45, API-30, API-31, API-36, ADR-06, ADR-52)
2026-10-16 14:25:35 +02:00
- ~~[LIM-10]~~ Rimosso: chiuso da B-82 e B-85. I depositi si accreditano a
`deposit_confirmations` conferme (le coinbase a 120), le riorganizzazioni sono
seguite, registrate e, da profondità 2, mettono in pausa; le monete del round
si verificano con il quorum prima del pagamento e i partecipanti spariti
restano fuori ([ADR-52 ](adr/ADR-52-riorganizzazioni-catena-contengono-invece-ignorarle.md )). Resta accettato il 51% ([LIM-24]).
2026-10-14 12:05:57 +02:00
2026-10-14 14:04:35 +02:00
- **[LIM-11]** **I server del quorum li sceglie l'operatore.** Il server
rifiuta di partire con meno di 2 server di riserva e ogni corroborazione
richiede almeno 2 risposte ([ADR-07 ](adr/ADR-07-ogni-decisione-sposta-denaro-base-risposta.md ), B-84), ma più server controllati dalla
stessa parte contano come voci indipendenti. I certificati TLS non sono
verificati contro un'autorità; si possono fissare per endpoint
(`host:porta:pin=<sha256>` , `ELECTRUM_CERT_PIN` ).
- *Impatto:* chi controlla la maggioranza dei server configurati (o
2026-10-14 12:05:57 +02:00
raggiungibili) può scegliere il vincitore o alterare i saldi mostrati.
2026-10-14 14:04:35 +02:00
- *Mitigazione attuale:* quorum minimo obbligatorio; meno di 2 risposte →
la corroborazione **fallisce** (una rete irraggiungibile non prova
nulla); pin facoltativo del certificato. Il vecchio caso «senza riserve
tutto passa» e «una sola risposta decide» è chiuso da B-84.
- *Per chiuderlo:* non si chiude: resta una scelta dell'operatore usare
server indipendenti fra loro.
2026-10-14 12:05:57 +02:00
*Codice:* `app/electrum/listener.py:ElectrumListener._corroborate_majority`
Credit deposits at deposit_confirmations and verify the round's coins with the quorum before paying (B-82, DEP-03, DEP-04, DEP-09, DEP-12, DEP-14, ELC-17, ELC-27, RND-21, RND-22, RND-25, RND-39, DAT-10, DAT-11, DAT-24, DAT-62, DAT-67, DAT-68, AUD-12, AUD-44, AUD-45, API-30, API-31, API-36, ADR-06, ADR-52)
2026-10-16 14:25:35 +02:00
- ~~[LIM-12]~~ Rimosso: chiuso da B-83 e B-82. Conferme (prova merkle),
reintegri, abbandoni e le monete del montepremi usate per il pagamento sono
tutti decisi dal quorum degli altri server, mai dal solo server attivo.
2026-10-14 12:05:57 +02:00
- **[LIM-13]** **L'estrazione attuale è manipolabile da chi produce o
trasmette i blocchi.**
- *Minatore:* il seme è il solo hash del blocco, quindi un minatore con
biglietti in gioco può trattenere i blocchi finché uno lo fa vincere. Non
è un'ipotesi remota: la potenza di calcolo di PLM (~22 TH/s) si può
affittare, e scartare qualche blocco costa solo le ricompense perse.
- *Server attivo:* può scegliere quale blocco reale usare semplicemente non
annunciandone uno; la corroborazione verifica che il blocco esista, non che
sia il primo dopo la chiusura, e dopo una corroborazione fallita si passa
comunque al blocco successivo.
- *Verificabilità:* l'ordine dei partecipanti è l'orario di invio registrato
dal server, un dato non pubblico; un giocatore non può ricalcolare da solo
l'esito (il commento di `draw_winner` dice il contrario).
- *Pareggi:* due scommesse con lo stesso `broadcast_at` non hanno un criterio
secondario di ordinamento, quindi l'estrazione non è riproducibile.
- *Distorsione del modulo:* un seme da 256 bit modulo al più 400 partecipanti
introduce una distorsione inferiore a 400/2²⁵⁶, accettata anche dopo il
piano.
Make the draw a commit-reveal at a fixed, confirmed, quorum-agreed height with a public proof (B-73, RND-14 … RND-20, RND-33 … RND-37, DAT-61 … DAT-66, API-25, API-45, AUD-24 … AUD-27, AUD-37, ELC-15, SEC-56, ADR-24, ADR-25)
2026-10-14 16:29:09 +02:00
- *Stato:* chiuso da B-73.
- *Come è chiuso:* il commit-reveal di [ADR-24 ](adr/ADR-24-l-estrazione-commit-reveal-fra-server.md ) con l'altezza fissa e le
2026-10-14 12:05:57 +02:00
conferme di [ADR-25 ](adr/ADR-25-blocco-estrazione-fissato-altezza-successivo-chiusura.md ): seme che dipende da un segreto impegnato
all'apertura, partecipanti ordinati per txid e fissati da un'impronta,
prova pubblica. Resta accettata la collusione fra operatore e minatore
([LIM-25]).
*Codice:* `app/rounds/draw.py:draw_winner` · `app/rounds/scheduler.py:RoundScheduler._close_and_draw`
- **[LIM-14]** **La validazione degli header non ricostruisce la difficoltà
2026-10-14 15:45:34 +02:00
reale.** Il bersaglio dichiarato non può essere più facile del limite di rete
(B-74, [ELC-11 ](connessione-electrum.md#4-header-validati-non-creduti )) e
ogni salto di più blocchi è verificato come catena, quindi un header falso a
un'altezza enorme non blocca più `tip_height` .
2026-10-14 12:05:57 +02:00
- *Mitigazione attuale:* la corroborazione del blocco usato per l'estrazione
2026-10-14 15:45:34 +02:00
([ADR-07 ](adr/ADR-07-ogni-decisione-sposta-denaro-base-risposta.md ), [ADR-25 ](adr/ADR-25-blocco-estrazione-fissato-altezza-successivo-chiusura.md )); non proteggeva dal blocco di `tip_height` , ora chiuso da B-74.
- *Stato:* chiuso da B-74.
- *Come è chiuso:* bersaglio non più facile di `powLimit` e catena
2026-10-14 12:05:57 +02:00
verificata su ogni salto ([ADR-26 ](adr/ADR-26-validazione-degli-header-concatenata-senza-ricalcolo.md )). Resta accettato dopo il piano: senza
il ricalcolo della difficoltà, un header a difficoltà minima (`powLimit` ,
in media 2³² hash) costa una frazione di secondo a un miner ASIC e pochi
secondi a una GPU, quindi un
server ostile può ancora produrre una breve catena falsa concatenata alla
nostra. A neutralizzarla sono il quorum sull'header di estrazione
([ADR-25 ](adr/ADR-25-blocco-estrazione-fissato-altezza-successivo-chiusura.md )), il segreto del commit-reveal ([ADR-24 ](adr/ADR-24-l-estrazione-commit-reveal-fra-server.md )) e il rilevamento delle
riorganizzazioni ([ADR-52 ](adr/ADR-52-riorganizzazioni-catena-contengono-invece-ignorarle.md )); una validazione completa con le regole di
ricalcolo della difficoltà di PLM resta fuori scopo.
*Codice:* `app/rounds/draw.py:header_meets_its_own_target` · `app/electrum/listener.py:ElectrumListener._apply_header`
- **[LIM-15]** **La sottoscrizione di un nuovo indirizzo è a sforzo
singolo.** Se fallisce subito dopo la registrazione, l'indirizzo resta non
osservato fino alla riconnessione successiva o al giro di `DepositReconciler`
(300s, che attende prima di partire).
- *Impatto:* un deposito fatto subito dopo la registrazione può tardare fino
a cinque minuti.
*Codice:* `app/electrum/listener.py:ElectrumListener.address_for_new_user` · `app/deposits/reconcile.py:DepositReconciler`
- **[LIM-24]** **Un attacco al 51% su PLM resta possibile: è contenuto, non
impedito.** Chi controlla la maggioranza della potenza di calcolo (che per
PLM, ~22 TH/s, si può affittare) può riscrivere più di 6 blocchi e
annullare depositi già accreditati o scommesse già confermate. Riguarda
chiunque usi PLM, non solo la lotteria.
- *Impatto:* un deposito accreditato e poi annullato da una riorganizzazione
più profonda di `deposit_confirmations` è denaro che la piattaforma ha già
contato; può essere stato scommesso o prelevato.
- *Mitigazione dopo il piano:* 6 conferme sui depositi ([ADR-06 ](adr/ADR-06-depositi-richiedono-deposit-confirmations-conferme-6.md )) rendono
l'attacco costoso quanto sei blocchi dell'intera rete; il rilevamento e la
pausa sulle riorganizzazioni profonde ([ADR-52 ](adr/ADR-52-riorganizzazioni-catena-contengono-invece-ignorarle.md )) lo rendono visibile e
fermano nuovi round; la verifica delle monete prima del pagamento fa sì
che un round colpito paghi ciò che resta invece di bloccarsi. Il denaro
sottratto dall'attaccante è perso per la piattaforma.
- *Aperto:* nessuna di queste misure lega il costo dell'attacco alla
dimensione del montepremi. Il tetto opzionale `max_round_pool_sats`
([ADR-21 ](adr/ADR-21-tre-limiti-distinti-sul-numero-monete.md )) è disattivato per default (0) e, anche impostato, il suo
valore non discende da alcun calcolo del costo di noleggiare potenza
sufficiente a riscrivere `deposit_confirmations` blocchi: resta a
discrezione dell'operatore. Per un montepremi abbastanza grande, quindi,
l'attacco può convenire nonostante le mitigazioni sopra. Decisione
rimandata: se e come legare `max_round_pool_sats` a una stima di quel
costo (o attivarlo per default) non è stato ancora deciso.
- *Per chiuderlo:* non è chiudibile dal software della lotteria.
*Codice:* `app/deposits/service.py:find_new_credit_candidates` · `app/electrum/listener.py:ElectrumListener._apply_header` · `app/rounds/scheduler.py:RoundScheduler._trigger_payout`
- **[LIM-25]** **Operatore e minatore insieme possono scegliere il
vincitore.** Con il commit-reveal ([ADR-24 ](adr/ADR-24-l-estrazione-commit-reveal-fra-server.md )) l'operatore conosce
`server_seed` e può calcolare l'esito di ogni blocco candidato; un minatore
d'accordo con lui può scartare i blocchi sfavorevoli. Lo stesso vale per chi
ottiene sia il database sia la chiave di cifratura: il segreto è cifrato a
riposo ([sicurezza.md ](sicurezza.md#sec-56 ) SEC-56), quindi il solo
database, o un suo backup, non basta.
- *Impatto:* un round può essere vinto da un complice.
- *Perché è accettato:* l'operatore è già fidato come custode di tutti i
fondi ([LIM-26]); chi può leggere il database può già fare di peggio.
Il commit-reveal impedisce a chi produce o trasmette i blocchi *da solo*
di scegliere il vincitore, che era il rischio da chiudere.
- *Per chiuderlo:* un contributo di casualità che l'operatore non conosce
(segreti dei giocatori, beacon esterno), scartato in [ADR-24 ](adr/ADR-24-l-estrazione-commit-reveal-fra-server.md ).
*Codice:* `app/rounds/draw.py:draw_winner` · `app/rounds/service.py:open_new_round_if_needed`
- **[LIM-27]** **Le soglie di conferma differiscono da quelle del portafoglio
di riferimento.** `PalladiumWallet` considera spendibile un'uscita normale
dopo `MinConfirmations = 6` conferme e una coinbase dopo 121 (maturità 120
più un blocco di margine). La piattaforma adotta 6 solo per i depositi
([ADR-06 ](adr/ADR-06-depositi-richiedono-deposit-confirmations-conferme-6.md )) e 120 per le coinbase; un prelievo o una vincita, confermati qui
a 1 conferma, compaiono nel portafoglio di riferimento dell'utente come non
ancora spendibili per altri 5 blocchi.
- *Impatto:* un utente che preleva verso `PalladiumWallet` vede i fondi
arrivati ma bloccati per altri 5 blocchi (10 minuti in media) rispetto a
quanto dice la piattaforma; nessun rischio per i fondi.
- *Per chiuderlo:* non serve: è una politica del portafoglio del
destinatario, non una regola di consenso. Le guide
([guida-utente.md ](guida-utente.md )) possono dirlo.
*Codice:* `app/tx/confirmation.py:poll_once` · `app/deposits/service.py:find_new_credit_candidates`
### Configurazione ed economia
Snapshot the bet cost, fee address and pool cap onto the round at open, and cap the pool (B-95, RND-05, RND-06, RND-23, BET-20, BET-21, DAT-26, DAT-27, DAT-61, DAT-62, API-21, API-30, API-31, ADR-10, LIM-16)
2026-10-16 15:12:00 +02:00
- ~~[LIM-16]~~ Rimosso: chiuso da B-95. Costo della scommessa, `fee_address` e
tetto al montepremi sono fotografati sulla riga del round all'apertura, come
durata e pausa ([ADR-10 ](adr/ADR-10-parametri-round-fotografati-all-apertura.md )); il fee rate resta letto dal vivo per scelta e il prelievo
minimo segue il costo corrente della configurazione ([ADR-20 ](adr/ADR-20-prelievo-minimo-costo-corrente-scommessa.md )).
2026-10-14 12:05:57 +02:00
- **[LIM-17]** **Nessun consolidamento automatico del montepremi.** Monete
Give every round its own pool address (B-80, RND-35, DAT-60, DAT-11, REG-14, SEC-08, BET-12, RND-22, WDR-14, SEC-43, API-23, API-36)
2026-10-14 13:52:32 +02:00
avanzate e resti si accumulano sugli indirizzi del montepremi (fino a B-80
su uno solo, per tutti i round); il margine fra 400 partecipanti e 500
ingressi ([ADR-21 ](adr/ADR-21-tre-limiti-distinti-sul-numero-monete.md )) si consumava nel tempo.
2026-10-14 12:05:57 +02:00
- *Impatto:* raggiunto il limite, il pagamento fallisce con
`too_many_inputs` e il round resta in `paying_out` , con un `payout_failed`
ogni 60s.
Give every round its own pool address (B-80, RND-35, DAT-60, DAT-11, REG-14, SEC-08, BET-12, RND-22, WDR-14, SEC-43, API-23, API-36)
2026-10-14 13:52:32 +02:00
- *Stato:* la parte del margine che si consumava è chiusa da B-80: con un
indirizzo per round ([ADR-03 ](adr/ADR-03-ogni-round-ha-proprio-indirizzo-montepremi.md )) il pagamento spende solo le monete del proprio
round, e il margine non si consuma più (resta lo stato descritto sopra per
i round aperti prima di B-80, che condividono l'indirizzo di riserva).
- *Resta accettato:* le riserve per l'RBF ([ADR-17 ](adr/ADR-17-commissione-pagamento-esce-dalla-quota-vincitore.md )) e le monete
2026-10-14 12:05:57 +02:00
estranee inviate agli indirizzi di round chiusi si accumulano senza
consolidamento automatico; recuperarle è un'operazione manuale
([operativita.md ](operativita.md#intervento-manuale-sui-fondi )).
*Codice:* `app/rounds/scheduler.py:RoundScheduler._trigger_payout` · `app/wallet/psbt_builder.py:build_payout_transaction`
### Prodotto e interfacce
- **[LIM-18]** **Il sistema presuppone un solo processo.** Canale SSE, blocchi
per-utente, limitatori di tentativi, cache dei QR e della chiave dell'account
sono in memoria.
- *Impatto:* con più worker, due costruzioni dello stesso utente potrebbero
scegliere le stesse monete, i limiti per IP si dividerebbero per worker e
gli eventi SSE arriverebbero solo ai client dello stesso worker.
- *Per chiuderlo:* canale condiviso (Redis pub/sub) e lock su database o
Redis. L'invariante del round attivo è già un indice e non rientra qui.
Il piano del 2026-09-28 **non** lo chiude: un solo processo e un solo file
SQLite restano la scelta di [ADR-13 ](adr/ADR-13-processo-file-sqlite.md ), adeguata alla scala prevista; il piano
rende solo più affidabile quel processo (B-88, B-93).
*Codice:* `app/tx/locks.py:UserLocks` · `app/rounds/events.py:RoundEventBroadcaster` · `app/auth/rate_limit.py:AuthRateLimiters`
- **[LIM-19]** **Nessuna cronologia per l'utente.** L'utente non vede lo storico
dei round, delle scommesse, dei depositi o dei prelievi: un prelievo fallito
lascia una riga `failed` che l'utente non può mai vedere. Esistono solo
`GET /users/me/last-round-result` (l'ultimo round giocato) e
`GET /bug-reports/mine` . L'operatore ha invece `/admin/rounds` ,
`/admin/pending-transactions` , `/admin/audit-log` .
- *Per chiuderlo:* endpoint e sezione di cronologia personale.
*Codice:* `app/api/routes/users.py:last_round_result` · `app/withdrawals/service.py:request_withdrawal`
- **[LIM-20]** ** `/guida` è un segnaposto.** Il collegamento funziona ma la
pagina dice che la guida arriverà. (`/report-bug` invece è completa e
tradotta.)
*Codice:* `app/main.py:user_guide` · `app/static/guida.html`
- **[LIM-21]** **La parità delle chiavi di traduzione non è verificata da un
test.** Oggi le 7 lingue hanno le stesse 151 chiavi, ma nulla lo impone: una
chiave mancante ricade sull'inglese e, se manca anche lì, viene mostrata la
chiave grezza.
- *Stato:* da chiudere con il test di parità di
[ARC-63 ](architettura.md#test ) (nessun B-nn: non è un difetto del codice).
- *Per chiuderlo:* un test che confronti gli insiemi di chiavi di
`TRANSLATIONS` .
*Codice:* `app/static/i18n.js:t`
- **[LIM-22]** **L'API di amministrazione e alcuni errori di contorno non usano
il formato d'errore strutturato.** Gli endpoint `/admin/*` rispondono con un
`detail` stringa (per esempio `"invalid admin token"` , `"user not found"` ), e
così il 503 del canale SSE oltre il tetto; è coerente con un pannello non
tradotto, ma non è dichiarato altrove.
- *Stato:* da chiudere con B-94.
- *Per chiuderlo:* errori strutturati ovunque: `/admin/*` , il 503 del canale
SSE e gli errori generati dal framework usano lo stesso formato
`{code, message, params}` ([ADR-47 ](adr/ADR-47-l-api-parla-sola-lingua-risponde.md )).
*Codice:* `app/api/routes/admin.py:require_admin` · `app/api/routes/rounds.py:round_stream`
- **[LIM-23]** ** `GET /admin/users` non ha limite né paginazione**, a
differenza degli altri elenchi admin.
- *Impatto:* con molti utenti la risposta cresce senza limite.
- *Stato:* da chiudere con B-94 (paginazione dell'elenco utenti).
*Codice:* `app/api/routes/admin.py:list_users`