An empty ELECTRUM_FALLBACK_SERVERS (the .env.example default) silently made every corroboration pass, and with fallbacks the majority was taken over whoever answered, so one reachable fallback decided alone. TLS certificates were never checked. - _corroborate_majority passes only with at least 2 answers (MIN_QUORUM_RESPONSES) and a strict majority of the answers; no other server configured, or fewer than 2 answering, fails. ALLOW_NO_QUORUM=true keeps the old reduced rule. - config.validate_quorum_servers, called from the lifespan after parse_endpoints, refuses to start with fewer than 2 fallback servers unless ALLOW_NO_QUORUM=true (which logs a warning at every start). - Optional pinning: `host:port[:mode]:pin=<sha256>` per fallback and ELECTRUM_CERT_PIN for the primary; ElectrumClient compares the SHA-256 of the DER certificate after the TLS handshake and a mismatch fails like an unreachable server. A pin on a plaintext entry is a startup error. - .env.example now ships the four bootstrap servers and documents both new variables; docs/setup.md, spec, storico and BUGS.md updated. - Tests changed because the spec changed: the two "no other servers configured -> True" corroboration tests now assert that outcome only under allow_no_quorum. Note: a dev .env with fewer than 2 fallback servers will now refuse to start. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
34 KiB
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/.
Un limite noto non è un bug: un bug è un comportamento che contraddice la
specifica e prende un numero B-nn (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.
Un limite che il piano delle correzioni 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. 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.
| Limite | Blocca il non presidiato oggi? | Dopo il piano |
|---|---|---|
[LIM-01] drawing non riprende dopo un errore o un riavvio |
Sì — è lo stato che resta fermo con denaro in gioco | Chiuso da B-81 |
| [LIM-02] RBF di una sola forma (e pagamento senza resto) | Sì, in caso di congestione della rete | Chiuso da B-79 e B-87 |
[LIM-03] Attesa in closing senza limite di tempo |
Sì, in caso di congestione | Resta senza limite di tempo per scelta (ADR-51); 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) prima di aprire al pubblico |
| [LIM-10] Riorganizzazioni non gestite | Sì — una scommessa sparita rende il round impagabile | Chiuso da B-82 e B-85; resta [LIM-24] |
| [LIM-11], [LIM-12] Quorum facoltativo e parziale | Sì — un solo server decide conferme e abbandoni | Chiusi da B-83 e B-84 |
| [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
-
[LIM-01] Lo stato
drawingnon riprende dopo un errore o un riavvio. Lo scheduler gestisceopen,closingepaying_outa ogni giro e saltadrawing; l'attesa del blocco (_wait_for_next_block) vive solo dentro la chiamata a_close_and_drawche l'ha avviata, contip_at_closee la lista dei partecipanti in memoria. Non serve un riavvio: qualunque eccezione durante l'attesa (undatabase is lockedscrivendo una riga di audit, un header malformato) la interrompe, e nessuno la riprende. Il terzo audit (2026-08-03) lo ha ribadito come problema critico, la revisione del 2026-09-28 lo ha numerato (B-81).- Impatto: il round resta in
drawingper sempre con il montepremi dentro; poiché un nuovo round si apre solo a round chiuso, l'intera lotteria si ferma. Conrestart: unless-stoppedun crash in quella finestra è esattamente il caso che non si ripara da solo. - Mitigazione attuale: l'attesa è osservabile — log periodici, evento
draw_stalleddopo 360s (e poi a ogni intervallo),draw_waiting_sincesuGET /rounds/current. Il recupero è manuale (vedi operativita.md). - Stato: da chiudere con B-81.
- Per chiuderlo: al passaggio in
drawingsi salvanodraw_height,participants_hashe la posizione di estrazione di ogni partecipante;_tickrientra nell'attesa per un round indrawing, in modo idempotente. Con l'altezza fissa e il seme impegnato di ADR-24/ADR-25, un'estrazione ripresa dà sempre lo stesso vincitore.
Codice:
app/rounds/scheduler.py:RoundScheduler._tick·app/rounds/scheduler.py:RoundScheduler._close_and_draw·app/rounds/scheduler.py:RoundScheduler._wait_for_next_block - Impatto: il round resta in
-
[LIM-02] L'RBF gestisce una sola forma di transazione: un'unica uscita di resto verso il mittente, abbastanza grande da assorbire l'aumento. Non c'è il ripiego «aggiungi un'altra moneta». 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 dal resto; quando l'aumento supera il resto,
bump_feesollevaRbfError. Inoltrebump_feenon controlla che il resto ridotto resti sopra la soglia di polvere: con un resto vicino al minimo di 294 sat (ADR-19) già il primo aumento produce un'uscita sotto soglia, che le regole di inoltro standard rifiutano. Il pagamento del round, nel caso normale, non ha alcun resto (spende esattamente il montepremi del round, e un resto sotto soglia va alla commissione), quindi non è accelerabile affatto.- Impatto: con la rete congestionata una transazione a 1 sat/vB 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 inpaying_out.RbfBumperregistra un errore ogni 30s. - Mitigazione attuale: resto minimo garantito sulle transazioni utente;
tetto al fee rate (
MAX_FEE_RATE_SAT_VB); abbandono dopo 6 ore per ciò che sparisce dalla rete; ritentativo automatico del pagamento dopo un abbandono. - Stato: da chiudere con B-79 e B-87.
- Per chiuderlo: ogni pagamento porta un'uscita di riserva che
l'accelerazione riduce (ADR-17, B-79); un aumento che lascerebbe un
resto sotto
DUST_LIMIT_SATSelimina il resto e ne versa il valore in commissione, e quella sostituzione è l'ultima (ADR-19, B-87); ogniRbfErrore ogni sostituzione rifiutata scrivonorbf_bump_failedin audit. Resta accettato, dopo il piano: nessun ripiego «aggiungi un'altra moneta» né CPFP; 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). Il docstring dibump_feecita ancora come esempio «una scommessa senza resto», caso che dopo B-62 non esiste più.
Codice:
app/tx/broadcast.py:bump_fee·app/wallet/psbt_builder.py:build_payout_transaction·app/tx/reconcile.py:reconcile_once - Impatto: con la rete congestionata una transazione a 1 sat/vB 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
-
[LIM-03] L'attesa in
closingnon ha un limite proprio. Il round aspetta ogni scommessabuilding/broadcast. Oggi a porre fine all'attesa di una scommessa che non conferma è solo il riconciliatore, dopo 6 ore dal primo invio se la transazione è sparita dalla rete del server attivo; una scommessa ferma ma presente in mempool trattiene il round senza limite ([LIM-02]).- 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. - Stato: da chiudere con B-78 (in parte, per scelta).
- 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), 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 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.
- 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.
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 (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. 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_updatedcon 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,
/adminnon collegato dalla pagina pubblica. Dettagli in 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); 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).
Codice:
app/auth/routes.py:login·app/api/routes/users.py:change_password -
[LIM-09] Il JWT sta in
localStoragee 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). - Per chiuderlo: togliere gestori e stili inline dalle SPA per poter
eliminare
'unsafe-inline', oppure spostare la sessione in un cookieHttpOnly.
Codice:
Caddyfile·app/static/app.js - Mitigazione attuale: CSP
-
[LIM-26] La piattaforma è custodiale: gli utenti devono fidarsi dell'operatore. Tutte le chiavi stanno sul server (ADR-01): l'operatore (o chi compromette il server) può spendere ogni fondo, leggere
server_seedprima 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), audit di ogni azione sensibile. Modello di minaccia in sicurezza.md.
- Per chiuderlo: un disegno non custodiale, scartato in ADR-01.
Codice:
app/wallet/hd.py:_load_account_key·app/wallet/keystore.py:decrypt_xprv
Fiducia nella rete
-
[LIM-10] Riorganizzazioni non gestite, depositi a 1 conferma. Un deposito accreditato a 1 conferma può sparire con una riorganizzazione; la punta della catena non regredisce mai, un header concorrente alla stessa altezza è ignorato e nessuna riorganizzazione viene rilevata.
- Impatto: il caso grave è una scommessa già confermata che sparisce:
un utente scommette con un deposito a 1 conferma e, se perde, sostituisce
il deposito con una catena privata. La scommessa non è più in catena, ma il
partecipante resta
confirmede contato, e il pagamento fallisce per sempre con «monete del montepremi insufficienti», con il denaro di tutti gli altri giocatori bloccato. Anche le uscite coinbase sono accreditate a 1 conferma, mentre maturano a 120. - Mitigazione attuale: una moneta che sparisce dall'elenco del server viene, previo quorum, segnata come spesa all'esterno e tolta dal saldo; se riappare viene ripristinata. Una scommessa non ancora inviata costruita su una moneta scomparsa è rifiutata dalla rete e annullata; per una scommessa già confermata non esiste alcuna mitigazione (la versione precedente di questa voce diceva il contrario).
- Stato: da chiudere con B-82 e B-85.
- Per chiuderlo: depositi a
deposit_confirmationsconferme e coinbase a 120 (ADR-06); riorganizzazioni rilevate, registrate e, se profonde, con pausa; monete del round verificate prima del pagamento, con i partecipanti spariti esclusi (ADR-52). Resta accettato il 51% ([LIM-24]).
Codice:
app/deposits/service.py:find_utxos_missing_from·app/deposits/service.py:reinstate_reappeared_utxos·app/electrum/listener.py:ElectrumListener._apply_header - Impatto: il caso grave è una scommessa già confermata che sparisce:
un utente scommette con un deposito a 1 conferma e, se perde, sostituisce
il deposito con una catena privata. La scommessa non è più in catena, ma il
partecipante resta
-
[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, 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 raggiungibili) può scegliere il vincitore o alterare i saldi mostrati.
- 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.
Codice:
app/electrum/listener.py:ElectrumListener._corroborate_majority -
[LIM-12] Non tutte le letture di rete sono corroborate. Passano dal solo server attivo: la conferma di scommesse, pagamenti e prelievi (
poll_once), la decisione del riconciliatore che una transazione «non esiste più», l'elenco delle monete del montepremi usato per il pagamento, e il ripristino di una moneta prima segnata come spesa all'esterno.- Impatto: un server ostile potrebbe far considerare confermata una
scommessa che non lo è, o far abbandonare a torto una transazione viva
(liberandone le monete e, per un pagamento, azzerando
payout_txid), lasciando il database in disaccordo con la catena; il ripristino non corroborato può reintegrare nel saldo una moneta davvero spesa. - Mitigazione attuale: l'abbandono avviene solo dopo 6 ore per
pending; un invio basato su monete inesistenti è rifiutato dalla rete. - Stato: da chiudere con B-83 (conferme e ripristini), B-78 (abbandono), B-82 (monete del round prima del pagamento) e B-76 (invio a tutti i server).
- Per chiuderlo: una conferma vale quando il quorum restituisce una prova
merkle valida (
blockchain.transaction.get_merkle) contro un header che possediamo; il ripristino richiede lo stesso quorum di un accredito; l'abbandono richiede la prova di ADR-51; le monete del round si verificano con il quorum prima di costruire il pagamento (ADR-52); ogni transazione accettata dal server attivo viene inviata anche a tutti i server di riserva.
Codice:
app/tx/confirmation.py:poll_once·app/tx/reconcile.py:reconcile_once·app/deposits/service.py:reinstate_reappeared_utxos·app/rounds/scheduler.py:RoundScheduler._trigger_payout - Impatto: un server ostile potrebbe far considerare confermata una
scommessa che non lo è, o far abbandonare a torto una transazione viva
(liberandone le monete e, per un pagamento, azzerando
-
[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_winnerdice il contrario). - Pareggi: due scommesse con lo stesso
broadcast_atnon 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.
- Stato: da chiudere con B-73.
- Per chiuderlo: il commit-reveal di ADR-24 con l'altezza fissa e le conferme di ADR-25: 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à reale. Oggi il bersaglio non è confrontato con il limite di rete: un server può dichiarare
bits=0x207fffffe l'header passa senza alcun lavoro (la versione precedente di questa voce parlava di «hardware modesto», sottostimando il problema). La concatenazione si verifica solo per un avanzamento di un blocco esatto, quindi un header falso a un'altezza enorme bloccatip_height, che non regredisce, e con esso ogni estrazione.- Mitigazione attuale: la corroborazione del blocco usato per l'estrazione
(ADR-07, ADR-25); non protegge dal blocco di
tip_height. - Stato: da chiudere con B-74.
- Per chiuderlo: bersaglio non più facile di
powLimite catena verificata su ogni salto (ADR-26). 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), il segreto del commit-reveal (ADR-24) e il rilevamento delle riorganizzazioni (ADR-52); 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 - Mitigazione attuale: la corroborazione del blocco usato per l'estrazione
(ADR-07, ADR-25); non protegge dal blocco di
-
[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) rendono l'attacco costoso quanto sei blocchi dell'intera rete; il rilevamento e la pausa sulle riorganizzazioni profonde (ADR-52) 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) è disattivato per default (0) e, anche impostato, il suo valore non discende da alcun calcolo del costo di noleggiare potenza sufficiente a riscriveredeposit_confirmationsblocchi: resta a discrezione dell'operatore. Per un montepremi abbastanza grande, quindi, l'attacco può convenire nonostante le mitigazioni sopra. Decisione rimandata: se e come legaremax_round_pool_satsa 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 - Impatto: un deposito accreditato e poi annullato da una riorganizzazione
più profonda di
-
[LIM-25] Operatore e minatore insieme possono scegliere il vincitore. Con il commit-reveal (ADR-24) l'operatore conosce
server_seede 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 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.
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.
PalladiumWalletconsidera spendibile un'uscita normale dopoMinConfirmations = 6conferme e una coinbase dopo 121 (maturità 120 più un blocco di margine). La piattaforma adotta 6 solo per i depositi (ADR-06) 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
PalladiumWalletvede 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) possono dirlo.
Codice:
app/tx/confirmation.py:poll_once·app/deposits/service.py:find_new_credit_candidates - Impatto: un utente che preleva verso
Configurazione ed economia
-
[LIM-16] Solo i tempi del round sono fotografati all'apertura. Costo della scommessa, fee rate e
fee_addresssono letti dal vivo.- Impatto: cambiare il costo a metà round fa pagare poste diverse a
giocatori con la stessa probabilità, e cambia subito il prelievo minimo;
cambiare
fee_addressa metà round devia la commissione del round in corso (la modifica è comunque registrata in audit). - Stato: da chiudere con B-95.
- Per chiuderlo: fotografare costo,
fee_addressedraw_confirmationssulla riga del round, come già si fa per durata e pausa (ADR-10). Il fee rate resta letto dal vivo per scelta, e il prelievo minimo segue il costo corrente della configurazione (ADR-20).
Codice:
app/bets/service.py:place_bet·app/rounds/scheduler.py:RoundScheduler._trigger_payout·app/withdrawals/service.py:request_withdrawal - Impatto: cambiare il costo a metà round fa pagare poste diverse a
giocatori con la stessa probabilità, e cambia subito il prelievo minimo;
cambiare
-
[LIM-17] Nessun consolidamento automatico del montepremi. Monete 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) si consumava nel tempo.
- Impatto: raggiunto il limite, il pagamento fallisce con
too_many_inputse il round resta inpaying_out, con unpayout_failedogni 60s. - Stato: la parte del margine che si consumava è chiusa da B-80: con un indirizzo per round (ADR-03) 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) e le monete estranee inviate agli indirizzi di round chiusi si accumulano senza consolidamento automatico; recuperarle è un'operazione manuale (operativita.md).
Codice:
app/rounds/scheduler.py:RoundScheduler._trigger_payout·app/wallet/psbt_builder.py:build_payout_transaction - Impatto: raggiunto il limite, il pagamento fallisce con
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, 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
failedche l'utente non può mai vedere. Esistono soloGET /users/me/last-round-result(l'ultimo round giocato) eGET /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-buginvece è 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 (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 undetailstringa (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).
Codice:
app/api/routes/admin.py:require_admin·app/api/routes/rounds.py:round_stream -
[LIM-23]
GET /admin/usersnon 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