Files
plm-lottery/docs/storico-bug.md
T
davideandClaude Sonnet 5.5 c1b192f6d1 Harden the container: non-root, pinned images and dependencies, the key in a file (B-91, SEC-51, SEC-52, SEC-53; closes B-71, B-72)
- 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>
2026-10-18 00:54:23 +02:00

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-nn va 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 in docs/, 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_secrets chiamata 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 /guida serve 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:

  • 8f3cdcb e 421fe72 sono commit di sola contabilità di BUGS.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 /guida serve 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.