Only the duration and the cooldown (and, since B-73, the draw confirmations) were copied onto the round when it opened. The cost of a bet and the fee address were read live, so an edit from /admin in the middle of a round gave the players of that round different stakes for the same chance and redirected the commission of the round in progress. - `rounds.bet_amount_sats`, `rounds.fee_address` and `rounds.max_round_pool_sats` are copied from round_config in the transaction that opens the round (Alembic a7d3e9f15b48: plain add_column, existing rounds backfilled with the current configuration, i.e. what they were using; the expression indexes untouched). `place_bet` charges the round's cost, `_trigger_payout` pays the round's fee address (an empty one on the row still stops the payout, as before), `GET /rounds/current` advertises the round's cost while one is active and the configured one otherwise. The minimum withdrawal and the fee rate stay live, by design (ADR-20, ADR-10). - `round_config.max_round_pool_sats` (0 = none, otherwise >= the bet cost and <= 4e15, in /admin and PUT/GET /admin/config): a bet is refused with `round_full` (params also carry `max_round_pool_sats`) once the sum of the round's participant rows, in-flight ones included, has reached the round's cap, before anything moves; the pool can exceed it by one bet at most. A cap under the bet cost is a 422 `max_round_pool_below_bet_amount`. - PUT /admin/config's payability check (RND-40) now also judges a fee-rate change against the bet cost the round in progress opened with, which can be lower than the configured one. - `error.round_full` no longer says "maximum of N players" (it also fires for the pool cap): the seven translations are generic. Ambiguity resolved: the spec said the cap is snapshotted (RND-05, BET-20, ARC) in some places and "applies immediately, also to the round in progress" in the round_config table of modello-dati; the snapshot wins, with a column on `rounds` (modello-dati/api/guide-admin brought in line). Tests changed because the spec changed: the payout fixtures seed the round's own fee address, and the old "fee address missing" payout test now empties the round's address (clearing it in the configuration mid-round must not stop the payout). New test_round_snapshot.py: the copy at open, edits reaching only the next round, every bet of a round paying the same stake whether the cost is raised or lowered meanwhile, the payout following the round's address (and surviving it being cleared in the configuration), the cap (exactly at the cap is full, in-flight bets count, no cap, a mid-round edit of the cap does not apply, the refused player's coins untouched), plus the route, the admin bounds and the two 422s, the fee-rate check against the round's own cost, and the migration backfill. Mutation-checked: charging the live cost, paying the live address, not copying the cap and `>` instead of `>=` each fail tests. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
33 KiB
Storico dei bug
Registro di tutti i difetti numerati B-nn trovati finora: cosa non andava,
quanto era grave, quale commit lo ha corretto e quale test impedisce che
ritorni. È la memoria storica del progetto; le regole valide oggi stanno negli
altri documenti della specifica, a cui la colonna Area rimanda per
prefisso (vedi processo-di-modifica.md).
Questo file non contiene requisiti e quindi non ha identificativi propri.
Convenzione
- Un difetto nuovo prende il prossimo numero libero, senza riusare né
rinumerare quelli esistenti. Il prossimo numero libero è
B-99. - Un commit dedicato per ogni difetto, con il numero nel messaggio (ad
esempio
Snapshot a round's timing when it opens (B-61)), cosìgit log --all --grep 'B-nn'ritrova sempre come è stato chiuso. - Un test di regressione per ogni difetto, che cita il numero nella sua docstring o in un commento. Dove un test automatico non è possibile (resa grafica nel browser, layout dell'immagine Docker) lo si dichiara, come nelle righe marcate manuale qui sotto.
- Il marcatore
B-nnva anche nel codice, in un commento accanto alla correzione: le centinaia di riferimenti sparsi in codice e test sono puntatori verso questo registro e verso la storia git. - Prima la specifica, se era sbagliata. Una correzione che riporta il
codice a ciò che i documenti già dicevano è solo un
B-nn. Se invece il difetto rivela che la specifica era sbagliata o incompleta, si corregge prima il requisito indocs/, come per qualunque altra modifica (processo-di-modifica.md). - I limiti accettati per scelta non sono bug e non prendono un
B-nn: stanno in limiti-noti.md.
Da dove vengono questi dati
I difetti sono stati tracciati in un file BUGS.md alla radice del
repository, riscritto a ogni audit:
| Versione | Commit | Contenuto |
|---|---|---|
| Audit 2026-07-26 | fb734bb, 9b6a4a2, 845ba98 |
B-01 … B-24, con gravità, correzione proposta e, nell'ultima versione, la nota Fixed di ciascuno |
| Audit 2026-07-27 | 43d2321, 7224ca0 |
B-25 … B-49 con gravità |
| Chiusura | 0d2fef6 |
file cancellato quando entrambe le liste erano vuote |
| Audit 2026-08-03 | 99d7a1e e successivi |
B-52 … B-72, ogni voce rimossa al momento della correzione |
| Revisione della specifica 2026-09-28 | 88221b0 e successivi |
B-73 … B-98, aggiunte in coda al file |
Per leggere una versione: git show <commit>:BUGS.md. Il BUGS.md attuale,
presente nel ramo main, contiene solo le voci ancora aperte (B-71 … B-98).
La gravità è quella registrata in BUGS.md al momento dell'audit. Il terzo
audit la definiva così, e la stessa scala vale in sostanza anche per i primi
due: critica = denaro bloccato o perso, oppure la lotteria si ferma;
alta = un controllo di sicurezza che non regge, o un difetto con effetti
seri sul denaro; media = comportamento errato con danni circoscritti;
bassa = igiene, oppure documentazione non allineata al codice.
Audit del 2026-07-26 (B-01 … B-24)
Primo audit completo del codice (commit di partenza d4e0974, 79 test verdi,
nessuno dei quali copriva questi casi). Le correzioni sono raggruppate per
tema in otto commit, fra cc88763 e dc4d576, e il loro esito è registrato
in 845ba98. La suite passò da 79 a 139 test.
La causa comune dei quattro difetti critici principali (B-01 … B-04) era che il codice considerava un invio come definitivo e la connessione come infallibile: non esisteva alcuna riconciliazione fra database e catena.
| B-nn | Difetto | Gravità | Commit | Test di regressione | Area |
|---|---|---|---|---|---|
| B-01 | Una connessione Electrum caduta bloccava per sempre l'intero server: nessuna riconnessione, nessun timeout sulle richieste, nessun keepalive | Critica | 7c4e998 Survive a dropped Electrum connection, and fall back to other servers |
test_electrum_client.py::test_request_times_out_instead_of_hanging_forever, ::test_wait_closed_resolves_when_the_read_loop_dies; test_electrum_listener.py::test_rotates_to_the_next_server_after_a_failed_session |
ELC |
| B-02 | Un RBF su una scommessa lasciava RoundParticipant.bet_txid sul vecchio txid, e il round restava in closing per sempre |
Critica | d528c5b Let the system recover from a broadcast that never confirms |
test_confirmation.py::test_bet_confirms_after_an_rbf_bump_changed_the_txid; test_broadcast.py::test_bump_fee_retargets_every_stored_txid |
TX, BET |
| B-03 | Un solo txid irrisolvibile interrompeva il rilevamento delle conferme per tutte le altre transazioni | Critica | d528c5b |
test_confirmation.py::test_one_unresolvable_candidate_does_not_block_the_others |
TX |
| B-04 | Nessuna riconciliazione: una transazione scartata dalla rete congelava per sempre le monete spese e i fondi dell'utente | Critica | cc88763 Make "one active round" a DB invariant and record why a tx failed; d528c5b |
test_reconcile.py |
TX |
| B-05 | fee_address e parametri numerici della configurazione non validati: un valore errato bloccava il pagamento o mandava la commissione a uno script non spendibile |
Critica | daf66fd Fix the round-open race, the advertised jackpot, and payout error handling; 85dce22 Validate admin config and registration input, and log config changes |
test_admin.py::test_config_rejects_unusable_values |
API, RND |
| B-06 | Venivano create uscite di resto sotto la soglia di polvere, rendendo la transazione non inoltrabile | Alta | b4d7038 Leave dust-sized change to the fee instead of creating it |
test_psbt_builder.py::test_a_below_dust_change_output_is_never_created |
BET, WDR |
| B-07 | Un invio rifiutato dalla rete arrivava al client come HTTP 500, fuori dal contratto d'errore strutturato | Alta | d528c5b |
test_bets.py::test_failed_broadcast_reports_the_broadcast_failed_code; test_withdrawals.py::test_failed_broadcast_marks_the_withdrawal_failed_and_frees_the_coins |
BET, WDR, API |
| B-08 | La scommessa veniva inviata prima di essere salvata: un errore al commit perdeva il denaro senza lasciare traccia | Alta | d528c5b |
test_bets.py::test_bet_is_persisted_before_it_is_broadcast, ::test_failed_broadcast_leaves_nothing_behind |
BET, TX |
| B-09 | Una corsa permetteva due round open contemporanei, che bloccavano per sempre i round successivi |
Alta | cc88763; daf66fd |
test_rounds_service.py::test_losing_the_open_race_reuses_the_winning_round, ::test_the_database_refuses_a_second_active_round |
RND, DAT |
| B-10 | PUT /admin/config non scriveva nulla nel registro di audit |
Alta | 85dce22 |
test_admin.py::test_config_update_is_audit_logged, ::test_pause_cannot_be_toggled_through_the_config_endpoint |
AUD |
| B-11 | Il montepremi mostrato non era l'importo che il vincitore avrebbe ricevuto | Alta | daf66fd |
test_rounds_route.py::test_jackpot_comes_from_the_participants_actual_bets |
RND, API |
| B-12 | La registrazione non validava né username né password, a differenza del cambio password | Alta | 85dce22; dc4d576 Format amounts, guard the loading state, and translate the new errors |
test_users.py::test_register_rejects_weak_credentials |
REG, SEC |
| B-13 | verify_password trasformava un hash salvato malformato in un 500 invece che in un 401 |
Media | 85dce22 |
test_security.py::test_verify_password_returns_false_for_an_unparseable_hash |
SEC |
| B-14 | Token admin confrontato con != invece che a tempo costante |
Media | 85dce22 |
coperto dai test di autenticazione admin esistenti (test_admin.py::test_admin_rejects_wrong_token); nessun marcatore B-14 nel codice |
SEC |
| B-15 | JWT_SECRET o XPRV_ENCRYPTION_KEY vuoti non venivano rifiutati all'avvio |
Media | 25f4a1c Refuse to start half-configured, and keep failures machine-readable |
test_config.py |
SEC |
| B-16 | GET /guida rispondeva 500 in Docker perché docs/ non era copiata nell'immagine |
Media | 25f4a1c (crash eliminato, 404 guide_unavailable); superato da 447bbba, che serve un segnaposto statico app/static/guida.html |
manuale (dipende dal layout dell'immagine); nessun marcatore B-16 nel codice | ARC, OPS |
| B-17 | La ricerca dell'uscita di resto nell'RBF poteva ridurre l'uscita del destinatario invece del resto (prelievo verso il proprio indirizzo) | Media | d528c5b |
test_withdrawals.py::test_withdrawal_to_own_address_is_rejected |
WDR, TX |
| B-18 | Sessione del database tenuta aperta durante le chiamate di rete del pagamento | Media | daf66fd |
coperto dai test dello scheduler esistenti (test_scheduler.py) |
RND, TX |
| B-19 | tip_height assegnato senza controllo di monotonia (reorg o cambio server lo facevano regredire) |
Media | 7c4e998 |
test_electrum_listener.py::test_tip_never_moves_backwards |
ELC |
| B-20 | PendingTransaction.replaced_by_txid mai scritto: colonna morta mostrata in /admin |
Bassa | d528c5b |
test_broadcast.py::test_bump_fee_retargets_every_stored_txid |
TX, DAT |
| B-21 | Variabile locale inutilizzata e accesso ad attributi di oggetti ORM staccati dalla sessione nel poller delle conferme | Bassa | d528c5b |
coperto da test_confirmation.py |
TX |
| B-22 | Importi mostrati con una divisione in virgola mobile: artefatti visibili agli utenti | Bassa | dc4d576 |
manuale (resa nel browser) | ARC |
| B-23 | Due withLoading concorrenti sullo stesso pulsante potevano lasciarlo bloccato sull'etichetta di caricamento |
Bassa | dc4d576 |
manuale (interazione nel browser) | ARC |
| B-24 | Il gestore globale delle eccezioni rispondeva con un detail stringa invece che strutturato |
Bassa | 25f4a1c |
test_rounds_route.py::test_unhandled_errors_use_the_structured_detail_shape |
API |
Note:
- B-09 fu corretto diversamente dal piano: invece di rendere lo scheduler l'unico a poter aprire un round, entrambi i chiamanti restano e il conflitto sull'indice unico viene recuperato con un numero limitato di tentativi.
- B-15 fu corretto con una validazione all'avvio (
validate_runtime_secretschiamata dal lifespan) invece che con un validatore Pydantic, per non rendere ogni import e l'intera suite di test dipendenti da un.env. - B-16 era stato dichiarato «parzialmente corretto per scelta»: la copia di
docs/nell'immagine fu scritta e poi ritirata su richiesta del proprietario. Oggi/guidaserve un segnaposto (vedi limiti-noti.md).
Audit del 2026-07-27 (B-25 … B-49)
Secondo audit completo: 25 difetti (4 critici, 6 alti, 7 medi, 8 bassi), con
139 test verdi al momento dell'audit. Corretti nel ramo audit-2026-07-27,
unito con 5cfe2d6; ognuno ha il proprio commit. La suite passò da 139 a 253
test.
L'osservazione finale di quell'audit resta valida: il codice era rigoroso sui guasti già capitati e silenzioso su quelli mai visti.
| B-nn | Difetto | Gravità | Commit | Test di regressione | Area |
|---|---|---|---|---|---|
| B-25 | Il pagamento non aveva la scrittura in due fasi (salvare prima, inviare poi) che avevano scommesse e prelievi | Critica | f13f685 Persist the payout before broadcasting it |
test_scheduler.py::test_trigger_payout_persists_before_broadcasting, ::test_trigger_payout_broadcast_failure_leaves_a_recoverable_row |
RND, TX |
| B-26 | Un errore transitorio al momento del pagamento, o un riavvio, lasciava il round in paying_out per sempre |
Critica | 50a43ae Retry a stuck payout automatically, and log every failure |
test_scheduler.py::test_tick_retries_a_stuck_paying_out_round_with_no_recent_failure, ::test_trigger_payout_logs_a_failure_when_not_connected |
RND |
| B-27 | Ogni RBF azzerava l'orologio di abbandono del riconciliatore, che quindi non scattava mai | Critica | 933760e Decouple the RBF abandon clock from the bump clock |
test_reconcile.py::test_abandons_a_repeatedly_bumped_tx_despite_a_recent_last_broadcast; test_broadcast.py::test_should_bump_measures_from_last_broadcast_not_first |
TX |
| B-28 | Un server Electrum ostile (o un attacco man-in-the-middle) poteva scegliere da solo il vincitore del round | Critica | 0ce0562 Validate Electrum headers and corroborate the draw's block |
test_electrum_listener.py::test_apply_header_rejects_one_that_fails_its_own_pow_target; test_scheduler.py::test_wait_for_next_block_retries_after_a_failed_corroboration; test_draw.py::test_header_meets_its_own_target_accepts_a_real_mined_header |
ELC, RND |
| B-29 | Una moneta assente da un solo listunspent veniva segnata come spesa all'esterno subito e irreversibilmente |
Alta | e8fdea0 Corroborate an external spend before marking a UTXO gone |
test_electrum_listener.py::test_refresh_user_does_not_mark_when_corroboration_fails; test_deposits.py::test_find_utxos_missing_from_skips_a_totally_empty_response |
DEP, ELC |
| B-30 | Nessuna riconciliazione dei depositi: una sottoscrizione persa significava depositi mai accreditati | Alta | 63df38d Add a periodic deposit reconciler, and stop losing subscribe tasks |
test_deposit_reconcile.py; test_electrum_listener.py::test_address_for_new_user_retains_and_logs_a_failed_subscribe_task |
DEP |
| B-31 | Alla riconnessione la risottoscrizione di tutti gli utenti era seriale e bloccava l'altezza della catena, e con essa un'estrazione in corso | Alta | 12df041 Resubscribe concurrently and in the background on reconnect |
test_electrum_listener.py::test_subscribe_all_users_bounds_concurrency, ::test_run_once_keeps_consuming_headers_while_resubscribing |
ELC |
| B-32 | bump_fee poteva ritentare per sempre sotto il minimo di commissione imposto da BIP125, senza tetto al fee rate |
Alta | 17c557b Meet BIP125's relay minimum on every RBF bump, and cap the fee rate |
test_broadcast.py::test_bump_fee_meets_bip125_minimum_when_old_fee_already_exceeds_target, ::test_bump_fee_refuses_once_at_the_max_fee_rate |
TX |
| B-33 | Nessuna protezione dai tentativi ripetuti sul login di un portafoglio custodiale | Alta | 16802ca Throttle login and registration with exponential backoff |
test_auth.py::test_login_locks_out_after_repeated_failures, ::test_login_failures_against_one_account_do_not_lock_out_another |
SEC |
| B-34 | Cambio password e reset admin non invalidavano le sessioni già emesse | Alta | 739fc9f Invalidate existing sessions on password change/reset |
test_users.py::test_change_password_invalidates_the_old_token_but_not_the_new_one; test_admin.py::test_admin_resets_user_password; test_security.py::test_jwt_decode_defaults_token_version_for_tokens_issued_before_it_existed |
SEC |
| B-35 | Ogni orario dell'API era senza fuso, e il frontend lo interpretava come ora locale | Media | bb8b712 Stamp UTC on naive API timestamps before serializing |
test_api_timeutil.py::test_naive_datetime_is_stamped_utc; test_users.py::test_me_created_at_is_utc_stamped |
API |
| B-36 | L'attesa del blocco per l'estrazione non aveva né timeout né visibilità | Media | 7fa26df Make a stalled draw wait observable |
test_scheduler.py::test_wait_for_next_block_logs_a_stall_audit_entry_past_the_threshold; test_rounds_route.py::test_draw_waiting_since_is_exposed_only_while_drawing |
RND, AUD |
| B-37 | Saldo mostrato e saldo spendibile divergevano, e l'errore «saldo insufficiente» non lo spiegava | Media | 0b44fe6 Distinguish a pending-only balance from a truly insufficient one |
test_withdrawals.py::test_withdrawal_distinguishes_pending_from_truly_insufficient_balance |
WDR, DEP |
| B-38 | Il tetto globale di 500 abbonati SSE era un modo gratuito per negare il canale in tempo reale a tutti | Media | 702b37b Cap SSE subscribers per client IP instead of only globally |
test_round_events.py::test_subscribe_evicts_the_same_ips_oldest_connection_past_its_cap |
SEC, ARC |
| B-39 | SQLite senza WAL né busy_timeout, con più attività che scrivono in parallelo |
Media | 97545ad Run SQLite in WAL mode with a busy_timeout |
test_db_base.py |
ARC, DAT |
| B-40 | bump_fee teneva aperta una sessione del database durante N chiamate di rete, e calcolava gli importi da un valore in virgola mobile |
Media | 08c566d Restructure bump_fee into three phases, drop float fee math |
test_broadcast.py::test_bump_fee_holds_no_session_open_during_network_calls |
TX |
| B-41 | Conferme e riconciliazione dipendevano da blockchain.transaction.get in modalità verbose, rifiutata da molti server, e l'abbandono si basava sul confronto testuale del messaggio d'errore |
Media | 4124dc0 Check confirmation/existence via scripthash history, not verbose replies |
test_confirmation.py::test_one_unresolvable_candidate_does_not_block_the_others; test_reconcile.py; test_pending_address.py |
TX, ELC |
| B-42 | /docs, ReDoc e lo schema OpenAPI esposti in produzione, endpoint admin compresi |
Bassa | 22e3cfb Disable Swagger/ReDoc/OpenAPI JSON by default |
test_docs_disabled.py |
SEC |
| B-43 | Nessun header di sicurezza HTTP (CSP, HSTS, X-Frame-Options…) |
Bassa | d60da11 Add baseline HTTP security headers in Caddy |
test_caddyfile_security_headers.py |
SEC |
| B-44 | README e docs/running-the-server.md documentavano un uvicorn --reload diretto, contro la regola «solo Docker» |
Bassa | a574db0 Align README and running-the-server.md with the Docker-only policy |
test_docs_no_bare_uvicorn.py |
OPS |
| B-45 | Endpoint admin di elenco senza limiti validati né paginazione | Bassa | 6045c89 Bound admin list endpoint limits, add status filter to pending-transactions |
test_admin.py::test_admin_list_endpoints_reject_out_of_range_limit, ::test_admin_pending_transactions_status_filter |
API |
| B-46 | secrets.compare_digest su una stringa con caratteri non ASCII sollevava TypeError (500 invece di 403) |
Bassa | 31bc9a3 Compare admin token as UTF-8 bytes to avoid TypeError on non-ASCII input |
test_admin.py::test_admin_rejects_non_ascii_token_with_403_not_500 |
SEC |
| B-47 | Colonne String senza lunghezza per testi lunghi (raw_tx_hex, payload_json) |
Bassa | 6a90136 Widen raw_tx_hex and payload_json from String to Text |
test_models_text_columns.py |
DAT |
| B-48 | Nessun limite al numero di monete in ingresso: un indirizzo frammentato erodeva la propria scommessa o il proprio prelievo in commissioni | Bassa | 4c80c1c Cap the number of inputs a transaction may spend |
test_psbt_builder.py::test_select_utxos_never_exceeds_the_input_cap; test_bets.py::test_place_bet_reports_a_too_fragmented_balance_distinctly |
BET, WDR |
| B-49 | I percorsi di annullamento di scommessa e prelievo non pubblicavano un aggiornamento SSE | Bassa | e7f844b Publish an SSE update from the bet/withdrawal rollback paths |
test_bets.py::test_failed_broadcast_publishes_an_sse_update; test_withdrawals.py::test_failed_broadcast_publishes_an_sse_update |
BET, WDR, ARC |
Fuori audit (B-50, B-51)
Trovati il 2026-07-31 osservando il sistema in funzione, fra il secondo e il
terzo audit; non sono mai comparsi in un BUGS.md e la gravità non è stata
registrata. Il terzo audit riparte «dall'ultimo difetto corretto, B-51».
| B-nn | Difetto | Gravità | Commit | Test di regressione | Area |
|---|---|---|---|---|---|
| B-50 | Il banner di vincita appariva prima che winner_amount_sats fosse noto, mostrando «Hai vinto! +— PLM» |
— | 9207bbc Don't reveal the win banner before winner_amount_sats is known |
manuale (solo app/static/app.js); nessun marcatore B-50 nel codice |
RND, ARC |
| B-51 | Il saldo in attesa contava due volte il resto di una scommessa o di un prelievo già accreditato dal listener | — | fe909be Don't double-count a bet/withdrawal's own change in pending balance |
test_balance.py::test_pending_balance_does_not_double_count_change_already_credited (senza marcatore B-51) |
DEP |
Audit del 2026-08-03 (B-52 … B-72)
Terzo audit completo (264 test verdi al momento dell'audit), aperto con
99d7a1e nel ramo audit-2026-08-03, poi confluito in main. Ogni difetto è
stato rimosso da BUGS.md nel suo stesso commit di correzione. Due voci
restano aperte.
L'audit ricorda anche un problema non numerato, perché già elencato fra i
limiti accettati: lo stato drawing non riprende dopo un riavvio (vedi
limiti-noti.md).
| B-nn | Difetto | Gravità | Commit | Test di regressione | Area |
|---|---|---|---|---|---|
| B-52 | Un round con più di ~50 partecipanti bloccava la piattaforma per sempre: il pagamento usava il limite di 50 monete in ingresso pensato per le transazioni utente | Critica | 025754c Cap participants per round and give the payout its own input limit |
test_bets.py::test_place_bet_rejects_the_bet_past_the_participant_cap; test_payout_builder.py::test_payout_spends_more_utxos_than_a_user_transaction_may; test_scheduler.py::test_trigger_payout_pays_a_round_with_more_participants_than_max_tx_inputs |
RND, BET |
| B-53 | Una scommessa poteva finire nel montepremi ed essere comunque esclusa dall'estrazione | Critica | 64f6229 Close the window where a bet pays into a round it was left out of |
test_bets.py::test_place_bet_refuses_when_the_round_closed_between_the_check_and_the_commit, ::test_place_bet_rechecks_the_deadline_after_building_the_transaction; test_scheduler.py::test_close_and_draw_waits_when_a_bet_appears_after_the_tick_check |
BET, RND |
| B-54 | X-Forwarded-For accettato alla cieca: ogni controllo basato sull'IP era aggirabile |
Alta | 907e32e Make X-Forwarded-For trustworthy instead of attacker-controlled |
test_client_ip.py::test_client_ip_takes_the_last_hop_of_a_forwarded_chain, ::test_client_ip_ignores_a_client_supplied_prefix; test_caddyfile_security_headers.py::test_forwarded_for_is_overwritten_with_the_real_peer |
SEC |
| B-55 | L'hashing Argon2 girava sull'event loop, bloccando l'intero processo a ogni login | Alta | 9c7befe Hash and verify passwords off the event loop |
test_security.py::test_hash_password_async_keeps_the_event_loop_free, ::test_verify_password_async_keeps_the_event_loop_free |
SEC |
| B-56 | Il dizionario dei contatori del limitatore di tentativi non veniva mai sfoltito | Alta | ab65728 Bound the rate limiter's bucket dict |
test_rate_limit.py::test_the_dict_is_capped_even_within_one_sweep_interval, ::test_spent_buckets_are_swept_on_the_next_failure |
SEC |
| B-57 | Username confrontati distinguendo maiuscole e minuscole, mentre la chiave del limitatore no | Alta | 5772135 Make usernames one case-insensitive namespace |
test_auth.py::test_registration_refuses_a_username_differing_only_by_case, ::test_login_accepts_the_username_in_any_case, ::test_the_database_itself_rejects_a_case_variant |
REG, SEC |
| B-58 | Il limite sulle registrazioni contava i successi come fallimenti ed era solo per IP | Alta | 0aac73e Bound registrations with a per-IP quota instead of failure backoff |
test_auth.py::test_registration_is_quota_limited_per_ip, ::test_failed_registrations_do_not_consume_the_quota |
SEC, REG |
| B-59 | L'accredito dei depositi non era corroborato dagli altri server, a differenza del rilevamento delle spese esterne | Alta | 6246b13 Corroborate deposit credits, not just external spends |
test_electrum_listener.py::test_refresh_user_does_not_credit_a_utxo_only_our_own_server_reports, ::test_refresh_user_credits_once_the_others_corroborate |
DEP, ELC |
| B-60 | POST /admin/bug-reports/{id}/status non scriveva nulla nel registro di audit |
Alta | 77e07e8 Audit-log bug report status changes |
test_bug_reports.py::test_status_change_is_audit_logged, ::test_setting_the_status_it_already_has_logs_nothing |
AUD |
| B-61 | Le modifiche alla configurazione si applicavano retroattivamente al round in corso | Media | 37cc5ee Snapshot a round's timing when it opens |
test_scheduler.py::test_tick_ignores_a_config_duration_edited_mid_round; test_rounds_service.py::test_a_new_round_snapshots_the_current_config_timing |
RND |
| B-62 | «Preleva tutto» produceva sempre una transazione senza resto, quindi impossibile da accelerare con RBF | Media | 8dd913e Always keep a change output, so every tx stays fee-bumpable |
test_psbt_builder.py::test_build_signed_transaction_refuses_an_amount_that_would_leave_no_change; test_withdrawals.py::test_full_balance_withdrawal_keeps_a_bumpable_change_output |
TX, WDR, BET |
| B-63 | Subito dopo la connessione, con tip_height == 0, un'estrazione poteva usare un blocco precedente alla chiusura |
Media | 8a0ebec Never let a draw be seeded by a block that predates the close |
test_scheduler.py::test_wait_for_next_block_never_seeds_the_draw_from_a_pre_close_block; test_electrum_listener.py::test_run_once_publishes_the_client_only_once_the_tip_is_known |
RND, ELC |
| B-64 | _apply_header accettava un header alla stessa altezza senza controllo di concatenazione |
Media | c0314e2 Never swap the tip's hash sideways, and never split it from its height |
test_electrum_listener.py::test_apply_header_ignores_a_competing_header_at_the_current_tip_height, ::test_apply_header_ignores_one_carrying_no_hex |
ELC |
| B-65 | /rounds/current contava i partecipanti non confermati, che estrazione e pagamento invece escludono |
Media | c4b2dc3 Advertise the jackpot that will actually be paid |
test_rounds_route.py::test_advertised_jackpot_covers_only_the_bets_that_will_be_paid |
RND, API |
| B-66 | Nulla impediva di aprire round senza fee_address configurato, rendendoli impagabili |
Media | 23d5879 Refuse to open a round that could not pay its winner |
test_rounds_service.py::test_withholds_new_round_while_no_fee_address_is_configured, ::test_a_round_in_progress_survives_the_fee_address_being_cleared; test_rounds_route.py::test_lottery_configured_flags_a_missing_fee_address |
RND |
| B-67 | /qr/{address} non autenticato, sincrono e validato solo nella forma |
Media | 5f6abe5 Validate and bound the QR endpoint |
test_qr.py |
API, SEC |
| B-68 | CLAUDE.md e README descrivevano uno stato che il codice aveva superato | Bassa | 162ceed Describe the code the docs actually ship with |
test_docs_current.py |
ARC |
| B-69 | Commenti nel codice non più veri | Bassa | 666cb1a Retire in-code comments that outlived what they described |
test_code_comments.py |
ARC |
| B-70 | Il diagramma diceva che prelievo e scommessa in corso non potevano coesistere, ma il codice non lo impone (e non deve) | Bassa | 526a649 Say what bets and withdrawals actually exclude |
test_withdrawals.py::test_withdrawal_is_allowed_while_a_bet_is_still_unconfirmed, ::test_the_docs_no_longer_claim_bets_and_withdrawals_are_exclusive |
WDR |
| B-71 | .env punta MASTER_KEY_PATH a una seconda copia della chiave master (e DATABASE_URL a un secondo database) invece che ai file in ./data/ letti dal container |
Bassa | aperto | — | OPS, SEC |
| B-72 | docs/setup.md presenta ancora l'installazione come «in locale o via Docker», contro la regola «solo Docker» |
Bassa | aperto | — | OPS |
Note:
8f3cdcbe421fe72sono commit di sola contabilità diBUGS.md(registrano gli hash delle correzioni di B-53 e B-54), non correzioni.- Il test di B-70 sulla documentazione legge la sezione «Scommessa e prelievo insieme» di scommesse-e-prelievi.md: quel titolo e le frasi che il test cerca vanno mantenuti.
Revisione della specifica del 2026-09-28 (B-73 … B-98)
Quarta revisione, condotta sulla specifica in docs/ invece che sul solo
codice: quattro analisi indipendenti (meccanica della coin, macchina a stati
del denaro, operatività, coerenza dei documenti), con le affermazioni critiche
verificate nel codice. B-98 è emerso durante la stesura. Il comportamento
atteso di ogni voce è già scritto nella specifica, nei requisiti marcati
da implementare; le decisioni e l'ordine di lavoro sono in
piano-correzioni.md, il dettaglio tecnico in
BUGS.md.
La stessa revisione ha giudicato sottostimata la gravità di B-71: con la chiave duplicata, la procedura di rotazione della chiave di cifratura cifrava di nuovo il file sbagliato (vedi operativita.md).
| B-nn | Difetto | Gravità | Commit | Test di regressione | Area |
|---|---|---|---|---|---|
| B-73 | L'estrazione dipende solo dall'hash del blocco: un minatore con biglietti o il server Electrum attivo possono pilotarla; ordine dei partecipanti non verificabile | Critica | da registrare | test_draw.py, test_draw_proof.py, test_scheduler.py, test_electrum_listener.py, test_rounds_service.py, test_migrations.py |
RND, ELC |
| B-74 | Header accettati senza controllo della difficoltà minima; un salto di più blocchi non viene collegato; una riorganizzazione di un blocco chiude la sessione | Critica | da registrare | test_electrum_listener.py, test_draw.py |
ELC |
| B-75 | Una risposta Electrum oltre 64 KiB chiude la connessione, e la cronologia dell'indirizzo del montepremi la supera dopo ~800 transazioni | Critica | da registrare | test_electrum_client.py, test_deposits.py, test_psbt_builder.py |
ELC, TX |
| B-76 | Un invio ambiguo (timeout, connessione caduta) viene annullato come se fosse rifiutato | Critica | da registrare | test_broadcast_outcome.py, test_electrum_client.py |
TX, BET, WDR |
| B-77 | Si ricorda solo l'ultimo txid di una transazione accelerata, e la sostituzione viene inviata prima di essere salvata | Critica | da registrare | test_tx_versions.py, test_migrations.py, test_broadcast.py |
TX |
| B-78 | Una transazione viene abbandonata sulla base del silenzio di un solo server | Critica | da registrare | test_reconcile_proof.py, test_reconcile.py, test_pause_reason.py, test_migrations.py |
TX, ELC |
| B-79 | Il pagamento di norma non ha resto, quindi non si può accelerare e può bloccare la lotteria | Critica | da registrare | test_payout_builder.py, test_broadcast.py, test_scheduler.py, test_rounds_service.py, test_admin.py, test_migrations.py |
RND, TX |
| B-80 | Un solo indirizzo del montepremi per tutti i round | Critica | da registrare | test_round_pool_address.py, test_pending_address.py, test_scheduler.py |
RND, BET, WDR |
| B-81 | drawing resta bloccato dopo qualsiasi eccezione, non solo dopo un riavvio |
Critica | da registrare | test_draw_proof.py |
RND |
| B-82 | Un deposito annullato da una riorganizzazione dopo la scommessa rende il round impagabile; coinbase accreditate a 1 conferma | Critica | da registrare | test_deposit_confirmations.py, test_pool_verification.py, test_electrum_listener.py, test_admin.py, test_users.py |
DEP, RND |
| B-83 | Conferme e reintegri si basano solo sul server attivo | Alta | da registrare | test_confirmation_proof.py, test_confirmation.py |
ELC, TX, DEP |
| B-84 | Il quorum può ridursi a un solo server o a nessuno senza avvisi; certificati TLS non verificati | Alta | da registrare | test_electrum_client.py, test_electrum_listener.py |
ELC, SEC |
| B-85 | Le riorganizzazioni della catena non vengono né seguite né segnalate | Alta | da registrare | test_electrum_listener.py |
ELC, RND |
| B-86 | Un server connesso ma fermo non viene mai sostituito | Alta | da registrare | test_electrum_listener.py |
ELC, OPS |
| B-87 | L'RBF può lasciare un resto sotto la soglia di dust | Alta | da registrare | test_rbf_dust.py, test_broadcast.py |
TX |
| B-88 | Le transazioni SQLite non sono quelle che il codice presuppone; synchronous=NORMAL può perdere gli ultimi commit |
Alta | da registrare | test_sqlite_transactions.py, test_db_base.py |
DAT, ARC |
| B-89 | Problemi della chiave rilevati solo al primo uso; gli script accettano qualunque xprv e scrivono file leggibili da tutti | Alta | aperto | — | SEC, OPS |
| B-90 | Il ripristino di un backup riapre round già pagati e riassegna indici di derivazione | Alta | aperto | — | OPS, REG |
| B-91 | Container eseguito come root, permessi larghi, dipendenze e immagini non fissate | Alta | aperto | — | SEC, OPS |
| B-92 | Nessuna verifica di salute o prontezza, stato Electrum invisibile, audit non filtrabile, log incompleti | Media | aperto | — | OPS, AUD, API |
| B-93 | Deploy senza arresto ordinato, rollback impossibile, SITE_ADDRESS che torna a localhost |
Media | aperto | — | OPS |
| B-94 | Cambio password, login e bug report senza limiti adeguati; errori non strutturati in alcune risposte | Media | aperto | — | SEC, API |
| B-95 | Costo della scommessa e fee_address letti dalla configurazione corrente invece che fotografati all'apertura |
Media | da registrare | test_round_snapshot.py, test_admin.py, test_rounds_route.py, test_scheduler.py |
RND, BET |
| B-96 | Incoerenze minori di stato (prelievo ridotto sotto il minimo, importi di un pagamento abbandonato, audit mancanti, attesa in closing senza segnali, …) |
Media | aperto | — | TX, BET, WDR, DEP |
| B-97 | Le migrazioni possono eliminare in silenzio gli indici su espressione | Media | da registrare | test_migrations.py |
DAT |
| B-98 | La selezione delle monete si ferma alla prima che copre l'importo e rifiuta una scommessa pagabile | Media | aperto | — | BET, WDR |
Stato attuale
- Corretti: B-01 … B-70, tranne B-16, che è chiuso nella sostanza (il
crash è eliminato e
/guidaserve un segnaposto) ma lascia la guida vera come limite accettato; dalla revisione del 2026-09-28: B-88, B-75, B-97, B-80, B-84, B-76, B-77, B-78, B-83, B-74, B-85, B-86, B-73, B-81, B-79, B-82, B-87, B-95. - Aperti: B-71, B-72 (gravità bassa, operativi o di documentazione) e B-73 … B-98 dalla revisione del 2026-09-28 tranne i corretti (0 critici, 3 alti, 5 medi), da implementare nell'ordine di piano-correzioni.md.
- Prossimo numero libero:
B-99.