- app runs as uid 1000; a one-shot data-permissions service chowns ./data to it and closes it to others (works on an older root-owned install with no host sudo); files are created 0600 (umask 077) - python:3.12-slim and caddy:2-alpine pinned by digest; dependencies installed from requirements.lock with --require-hashes - the Fernet key is XPRV_ENCRYPTION_KEY_FILE (./secrets/xprv_encryption.key, a compose secret), out of every environment variable and of a ./data backup - setup.md no longer presents a second way to run the server (B-72) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
34 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 | corretto con l'allineamento dei file e B-89 | test_master_key_check.py |
OPS, SEC |
| B-72 | docs/setup.md presenta ancora l'installazione come «in locale o via Docker», contro la regola «solo Docker» |
Bassa | da registrare | — (documentazione) | 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-100)
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 | da registrare | test_master_key_check.py |
SEC, OPS |
| B-90 | Il ripristino di un backup riapre round già pagati e riassegna indici di derivazione | Alta | da registrare | test_restore_gap.py |
OPS, REG |
| B-91 | Container eseguito come root, permessi larghi, dipendenze e immagini non fissate | Alta | da registrare | test_container_hardening.py, test_config.py |
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 | da registrare | test_psbt_builder.py |
BET, WDR |
| B-99 | Un server Electrum di Bitcoin supera ogni controllo sugli header (PLM ne condivide genesis e powLimit): il genesis non veniva verificato |
Media | da registrare | test_electrum_client.py |
ELC |
| B-100 | Il tetto del fee rate coincide con il maxfeerate del nodo (10 000 sat/vB): una tariffa al tetto, con vsize stimato per eccesso, verrebbe rifiutata |
Bassa | da registrare | — (costante; copertura da test_rbf_dust.py) |
TX |
Stato attuale
- Corretti: B-01 … B-72, 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-89, B-90, B-91, B-95, B-98, B-99, B-100. - Aperti: dalla revisione del 2026-09-28, B-92, B-93, B-94 e B-96 (0 critici, 0 alti, 4 medi), da implementare nell'ordine di piano-correzioni.md.
- Prossimo numero libero:
B-101.