Files
plm-lottery/docs/audit-log.md
T
davideandClaude Opus 5.5 61efbb0e37 Replace flowchart/ with a traceable specification in docs/
The two Mermaid diagrams under flowchart/ described the intended flow but
had drifted from the code and carried no link back to it. docs/ now holds
the specification instead: 15 Italian documents (flows, architecture, data
model, API, security, audit log, operations, decisions and limits, bug
history) with 472 numbered requirements, each pointing at the function
that implements it and the test that covers it. docs/processo-di-modifica.md
defines the ID convention and the spec -> code -> test change workflow, and
CLAUDE.md now names docs/ as authoritative.

Writing the spec from the code also corrected stale claims in CLAUDE.md
(B-41's get_history check, BUGS.md still tracking B-71/B-72, Cache-Control
scope, the error contract's bare-string exceptions) and in the operator
guides (payouts do retry, only `drawing` fails to resume, rounds do not
open without fee_address). The B-70 doc test now pins the prose in
docs/scommesse-e-prelievi.md instead of the deleted diagram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:16:01 +02:00

26 KiB

Registro di audit

Il registro di audit è la traccia, nel database, di ciò che ha mosso denaro o cambiato la configurazione: scommesse, prelievi, depositi accreditati, estrazioni, pagamenti, interventi dell'operatore. È il primo posto da guardare per ricostruire cosa è successo dopo un problema, ed è l'unica forma di responsabilità che esiste per le azioni dal pannello admin, che non ha un'identità per singolo operatore (vedi sicurezza.md).

Questo documento descrive come il registro viene scritto e letto, e cataloga ogni tipo di evento. Le colonne della tabella audit_log sono descritte in modello-dati.md; le procedure che partono da un evento (per esempio un payout_failed) sono in operativita.md.

Come si scrive

  • [AUD-01] Ogni riga di audit nasce da un'unica funzione, write_audit_log(session, event_type, payload, user_id=None, round_id=None). La funzione non fa commit: aggiunge la riga alla sessione del chiamante, che la salva insieme al resto delle sue modifiche. Nessun altro punto del codice crea righe AuditLog. Codice: app/audit/log.py:write_audit_log

  • [AUD-02] Per gli eventi che accompagnano un cambiamento di stato, la riga di audit è salvata nella stessa transazione del cambiamento che descrive: o si salvano entrambi, o nessuno dei due. Vale per tutti gli eventi del catalogo tranne quelli elencati in [AUD-03]. Codice: i chiamanti elencati nel catalogo, ognuno dei quali chiama write_audit_log e poi session.commit() sulla stessa sessione.

  • [AUD-03] Quattro eventi sono solo osservativi: non accompagnano un cambiamento di stato e vengono salvati in una transazione propria, aperta apposta. Sono draw_baseline_tip_unknown, draw_header_corroboration_failed, draw_stalled e payout_failed. Per payout_failed la scrittura è anche best effort: se fallisce, l'errore viene registrato nel log applicativo (could not record the payout failure of round …) e ignorato. Codice: app/rounds/scheduler.py:RoundScheduler._wait_for_next_block, app/rounds/scheduler.py:RoundScheduler._adopt_baseline_tip, app/rounds/scheduler.py:RoundScheduler._log_payout_failure

  • [AUD-04] Il payload è un dizionario serializzato in JSON nella colonna payload_json; created_at è impostato dal database all'inserimento, in UTC. user_id e round_id sono chiavi esterne opzionali, valorizzate come indicato evento per evento nel catalogo. Codice: app/audit/log.py:write_audit_log, app/db/models.py:AuditLog

  • [AUD-05] Il registro è in sola aggiunta: nessuna parte del codice modifica o cancella una riga di audit_log, e non c'è alcuna rotazione o scadenza. La tabella cresce per sempre. Codice: app/audit/log.py:write_audit_log (unico punto di scrittura)

  • [AUD-06] event_type è una colonna String(32). Il nome più lungo oggi in uso, draw_header_corroboration_failed, occupa esattamente 32 caratteri: un nuovo tipo di evento deve restare entro questo limite. SQLite non lo fa rispettare, un altro database lo farebbe. Codice: app/db/models.py:AuditLog

Come si legge

  • [AUD-07] GET /admin/audit-log (protetto da X-Admin-Token) restituisce le righe dalla più recente (ordine per id decrescente), con il parametro limit (predefinito 200, minimo 1, massimo 500). Ogni elemento contiene id, event_type, payload (già decodificato da JSON), user_id, round_id e created_at in ISO 8601 UTC. Non esistono filtri per tipo di evento, utente o round, né paginazione: le righe più vecchie delle ultime 500 si leggono solo direttamente dal database. Codice: app/api/routes/admin.py:list_audit_log · Test: tests/unit/test_admin.py

  • [AUD-08] La sezione "Audit log" di /admin chiama l'endpoint senza parametri (quindi le ultime 200 righe) al login e ogni volta che si apre la sezione, e mostra id, tipo, payload JSON grezzo, utente, round e data. Non ha filtri né ricerca. Codice: app/static/admin.js:loadAuditLog

  • [AUD-09] Una sola logica del programma rilegge il registro: _retry_payout_if_due usa la riga payout_failed più recente del round come orologio per decidere se ritentare il pagamento. Se è più recente di 60 secondi (_PAYOUT_RETRY_INTERVAL_SECONDS) il tentativo viene saltato; se non esiste, il tentativo parte subito. Nessun'altra decisione dipende dal registro. Codice: app/rounds/scheduler.py:RoundScheduler._retry_payout_if_due · Test: tests/unit/test_scheduler.py

Catalogo degli eventi

Legenda delle colonne user_id / round_id: sì = sempre valorizzato, no = sempre vuoto, dipende = come spiegato nel testo. Gli importi sono sempre in satoshi.

flowchart LR
    subgraph Operatore["Pannello admin"]
        config_updated
        lottery_paused
        lottery_resumed
        admin_privkey_accessed
        admin_password_reset
        bug_report_status_changed
    end
    subgraph Fondi["Denaro degli utenti"]
        deposit_credited
        utxo_spent_externally
        utxo_external_spend_reinstated
        bet_placed
        bet_broadcast_failed
        withdrawal_sent
        withdrawal_broadcast_failed
    end
    subgraph Round["Round"]
        round_closed
        winner_drawn
        draw_baseline_tip_unknown
        draw_header_corroboration_failed
        draw_stalled
        payout_failed
        payout_sent
    end
    subgraph Ricon["Riconciliazione"]
        pending_tx_recovered
        pending_tx_abandoned
    end

Pannello admin

  • [AUD-10] config_updated — un PUT /admin/config ha cambiato almeno un parametro. Payload: un oggetto con una chiave per ogni campo effettivamente cambiato, ciascuna {"from": <valore precedente>, "to": <valore nuovo>} (i campi sono quelli di _CONFIG_FIELDS: fee_address, bet_amount_sats, round_duration_seconds, round_cooldown_seconds, fee_rate_sat_vb, rbf_timeout_seconds, draw_animation_seconds). Un campo inviato con lo stesso valore che aveva non compare; se nulla cambia, non si scrive nessuna riga. user_id no, round_id no. Stessa transazione della modifica. Codice: app/api/routes/admin.py:update_config · Test: tests/unit/test_admin.py

  • [AUD-11] lottery_paused — POST /admin/pause. Payload vuoto {}. user_id no, round_id no. Viene scritto a ogni chiamata, anche se la lotteria era già in pausa. Stessa transazione di paused = true. Codice: app/api/routes/admin.py:pause_lottery · Test: nessun test verifica la riga di audit.

  • [AUD-12] lottery_resumed — POST /admin/resume. Payload vuoto {}. user_id no, round_id no. Scritto a ogni chiamata, anche se la lotteria non era in pausa. Stessa transazione di paused = false. Codice: app/api/routes/admin.py:resume_lottery · Test: nessun test verifica la riga di audit.

  • [AUD-13] admin_privkey_accessed — GET /admin/users/{id}/privkey ha esportato la chiave privata (WIF) di un utente. Payload {"user_id"}. user_id sì (l'utente la cui chiave è stata esportata), round_id no. La riga è salvata prima che la risposta con la chiave venga restituita: se il salvataggio fallisce, la chiave non esce. Per un utente inesistente (404) non si scrive nulla. Questa registrazione non va mai rimossa (vedi decisioni-e-limiti.md). Codice: app/api/routes/admin.py:user_privkey · Test: nessun test verifica la riga di audit (tests/unit/test_admin.py copre solo l'esportazione).

  • [AUD-14] admin_password_reset — POST /admin/users/{id}/reset-password ha impostato una nuova password casuale. Payload {"user_id"}; la nuova password non compare. user_id sì, round_id no. Stessa transazione del nuovo hash e dell'incremento di token_version (che invalida le sessioni esistenti). Codice: app/api/routes/admin.py:reset_user_password · Test: nessun test verifica la riga di audit.

  • [AUD-15] bug_report_status_changed — POST /admin/bug-reports/{id}/status ha cambiato lo stato di una segnalazione. Payload {"report_id", "from", "to"} con gli stati open/read/resolved. user_id dipende: è l'autore della segnalazione, vuoto se era anonima; round_id no. Se lo stato richiesto è quello già presente non si scrive nulla. Stessa transazione del cambio di stato. Codice: app/api/routes/admin.py:update_bug_report_status · Test: tests/unit/test_bug_reports.py

Denaro degli utenti

  • [AUD-16] deposit_credited — un nuovo UTXO confermato sull'indirizzo di un utente è stato accreditato, dopo la corroborazione degli altri server (vedi registrazione-e-depositi.md). Una riga per UTXO. Payload {"txid", "vout", "amount_sats"}. user_id sì, round_id no. Stessa transazione dell'inserimento in utxo_events e del ricalcolo del saldo. Attenzione: non riguarda solo i depositi dall'esterno. Anche le vincite (l'uscita del pagamento verso il vincitore) e i resti di scommesse e prelievi arrivano all'indirizzo dell'utente e vengono accreditati per la stessa strada, quindi producono anche loro un deposit_credited. Codice: app/deposits/service.py:credit_confirmed_utxos · Test: nessun test verifica la riga di audit.

  • [AUD-17] utxo_spent_externally — un UTXO che il database considerava non speso non è più tra quelli non spesi, secondo il server attivo e la maggioranza degli altri: è stato speso fuori dalla piattaforma (per esempio con la chiave esportata). Una riga per UTXO. Payload {"txid", "vout", "amount_sats"}. user_id sì, round_id no. Stessa transazione della marcatura (spent_txid = "external-spend") e del ricalcolo del saldo. Codice: app/deposits/service.py:mark_utxos_spent_externally · Test: tests/unit/test_deposits.py

  • [AUD-18] utxo_external_spend_reinstated — un UTXO segnato come speso all'esterno è ricomparso tra i non spesi e la marcatura è stata annullata. Payload {"txid", "vout", "amount_sats"}. user_id sì, round_id no. Stessa transazione dell'annullamento e del ricalcolo del saldo. Codice: app/deposits/service.py:reinstate_reappeared_utxos · Test: tests/unit/test_deposits.py

  • [AUD-19] bet_placed — la rete ha accettato la transazione di una scommessa. Payload {"txid", "amount_sats"}: txid è quello del primo invio (un successivo RBF non aggiorna la riga), amount_sats è l'importo che arriva al montepremi, cioè la puntata al netto della commissione di rete. user_id sì, round_id sì. Stessa transazione che porta il partecipante a broadcast e la transazione pendente a pending. Codice: app/bets/service.py:place_bet · Test: tests/unit/test_bets.py

  • [AUD-20] bet_broadcast_failed — la rete ha rifiutato la transazione di una scommessa; gli UTXO sono stati liberati e il partecipante rimosso. Payload {"txid", "reason"}, con reason il testo dell'errore troncato a 200 caratteri. user_id sì, round_id no (a differenza di bet_placed). Stessa transazione del ripristino. Codice: app/bets/service.py:_release_failed_bet · Test: tests/unit/test_bets.py

  • [AUD-21] withdrawal_sent — la rete ha accettato la transazione di un prelievo. Payload {"txid", "amount_sent_sats", "external_address"}: amount_sent_sats è quanto arriva davvero all'indirizzo esterno (al netto della commissione e dell'eventuale limite di polvere, vedi scommesse-e-prelievi.md). L'id del prelievo non è nel payload. user_id sì, round_id no. Stessa transazione che porta il prelievo a broadcast. Codice: app/withdrawals/service.py:request_withdrawal · Test: nessun test verifica la riga di audit.

  • [AUD-22] withdrawal_broadcast_failed — la rete ha rifiutato la transazione di un prelievo; gli UTXO sono stati liberati e il prelievo segnato failed. Payload {"txid", "reason"} (reason troncato a 200 caratteri). user_id sì, round_id no. Stessa transazione del ripristino. Codice: app/withdrawals/service.py:_release_failed_withdrawal · Test: nessun test verifica la riga di audit.

Round

  • [AUD-23] round_closed — un round è stato chiuso senza partecipanti confermati. Payload {"participants": 0}. user_id no, round_id sì. Stessa transazione del passaggio a closed. È l'unico caso in cui la chiusura di un round viene registrata: un round chiuso dopo la conferma del pagamento non produce alcuna riga (vedi [AUD-32]). Codice: app/rounds/scheduler.py:RoundScheduler._close_and_draw · Test: tests/unit/test_scheduler.py

  • [AUD-24] winner_drawn — il vincitore è stato estratto. Payload {"winner_address", "pool_amount_sats", "block_height", "block_hash", "participants"}: indirizzo del vincitore, montepremi totale (somma delle puntate nette), altezza e hash del blocco usato come seme, numero di partecipanti. user_id sì (il vincitore), round_id sì. Stessa transazione che salva i dati dell'estrazione sul round e lo porta a paying_out. Codice: app/rounds/scheduler.py:RoundScheduler._close_and_draw · Test: tests/unit/test_scheduler.py

  • [AUD-25] draw_baseline_tip_unknown — al momento della chiusura l'altezza della catena non era nota, quindi il primo blocco ricevuto è stato preso come base e l'estrazione aspetta il blocco successivo. Payload {"baseline_height"}. user_id no, round_id sì. Transazione propria ([AUD-03]). Dovrebbe essere irraggiungibile (lo scheduler non lavora senza un'altezza nota), vedi ciclo-di-vita-del-round.md. Codice: app/rounds/scheduler.py:RoundScheduler._adopt_baseline_tip · Test: tests/unit/test_scheduler.py

  • [AUD-26] draw_header_corroboration_failed — il blocco candidato per l'estrazione non è stato confermato dalla maggioranza degli altri server Electrum (o nessuno ha risposto); l'estrazione aspetta un blocco ulteriore. Una riga per ogni blocco candidato rifiutato. Payload {"height", "reported_hash"}. user_id no, round_id sì. Transazione propria ([AUD-03]). Codice: app/rounds/scheduler.py:RoundScheduler._wait_for_next_block · Test: tests/unit/test_scheduler.py

  • [AUD-27] draw_stalled — l'estrazione aspetta un nuovo blocco da più di 360 secondi (_DRAW_STALL_THRESHOLD_SECONDS), contati da drawing_started_at; la riga si ripete ogni 360 secondi finché l'attesa continua. Payload {"tip_at_close", "current_tip_height", "elapsed_seconds"}. Nota: tip_at_close è la base attuale dell'attesa, che dopo un draw_header_corroboration_failed diventa l'altezza del candidato rifiutato, non più quella della chiusura. user_id no, round_id sì. Transazione propria ([AUD-03]). Non viene scritta mentre è in corso una corroborazione, solo mentre si aspetta un blocco. Codice: app/rounds/scheduler.py:RoundScheduler._wait_for_next_block · Test: tests/unit/test_scheduler.py

  • [AUD-28] payout_failed — un tentativo di pagamento non è partito. Payload {"round_id", "reason"}. user_id dipende: il vincitore, tranne quando il motivo è l'assenza di connessione (vuoto); round_id sì. Transazione propria e best effort ([AUD-03]); è l'orologio del nuovo tentativo ([AUD-09]). Valori possibili di reason:

    reason Causa
    electrum client not connected Nessuna connessione Electrum al momento del tentativo
    no fee_address configured fee_address vuoto nella configurazione
    winner user not found L'utente vincitore non esiste più nel database
    insufficient pool UTXOs Gli UTXO confermati e non riservati dell'indirizzo del montepremi non coprono l'importo
    too_many_inputs Servirebbero più di MAX_PAYOUT_TX_INPUTS (500) input
    winner_share_below_network_fee La quota del vincitore non copre la commissione di rete
    commission_below_dust_limit La quota della piattaforma è sotto il limite di polvere
    payout build failed Qualunque altro errore nella costruzione (per esempio un fee_address malformato)
    broadcast rejected La rete ha rifiutato la transazione; la riga building resta al riconciliatore

    Un tentativo saltato perché esiste già un pagamento building o pending per il round non scrive payout_failed. Cosa fare per ogni motivo è descritto in operativita.md. Codice: app/rounds/scheduler.py:RoundScheduler._trigger_payout, app/rounds/scheduler.py:RoundScheduler._log_payout_failure · Test: tests/unit/test_scheduler.py

  • [AUD-29] payout_sent — la rete ha accettato la transazione di pagamento. Payload {"txid", "winner_sats", "commission_sats"}: quanto riceve il vincitore (già al netto della commissione di rete) e quanto va a fee_address. user_id sì (il vincitore), round_id sì. Stessa transazione che porta la transazione pendente da building a pending. Codice: app/rounds/scheduler.py:RoundScheduler._trigger_payout · Test: tests/unit/test_scheduler.py

Riconciliazione

  • [AUD-30] pending_tx_recovered — una transazione rimasta building (tipicamente per un riavvio a metà invio) risulta invece presente in catena ed è stata promossa a pending. Payload {"pending_transaction_id", "kind", "txid"}. user_id e round_id dipendono dal tipo: sono quelli della riga pending_transactions (per un pagamento user_id è vuoto, per un prelievo round_id è vuoto). Stessa transazione della promozione. Vedi transazioni-e-riconciliazione.md. Codice: app/tx/reconcile.py:_promote · Test: nessun test verifica la riga di audit.

  • [AUD-31] pending_tx_abandoned — una transazione building o pending non compare nella cronologia del proprio indirizzo ed è stata dichiarata persa: stato failed, UTXO liberati, partecipante rimosso o prelievo failed, payout_txid del round azzerato. Payload {"pending_transaction_id", "kind", "txid", "reason", "utxos_released"}, con reason oggi sempre "not found on chain" e utxos_released il numero di UTXO liberati (sempre 0 per un pagamento, perché l'indirizzo del montepremi non è tracciato in utxo_events). user_id e round_id come in [AUD-30]. Stessa transazione dell'abbandono. Codice: app/tx/reconcile.py:_abandon · Test: tests/unit/test_reconcile.py

Cosa non viene registrato

  • [AUD-32] Questi fatti non producono una riga di audit; restano ricostruibili solo dallo stato delle tabelle o dal log applicativo:
    • apertura di un round;
    • passaggi di stato del round a closing e drawing, e la chiusura dopo la conferma del pagamento (_on_payout_confirmed);
    • conferma di una scommessa, di un prelievo o di un pagamento (ConfirmationPoller);
    • ogni aumento di commissione RBF e ogni RBF impossibile (solo log);
    • registrazione, login, login falliti e blocchi per troppi tentativi;
    • cambio password fatto dall'utente (POST /users/me/change-password);
    • invio di una segnalazione di bug;
    • accesso in lettura al pannello admin (elenchi utenti, round, audit). Codice: app/rounds/service.py:open_new_round_if_needed, app/rounds/scheduler.py:RoundScheduler._tick, app/rounds/confirmation.py:_on_payout_confirmed, app/tx/confirmation.py:poll_once, app/tx/broadcast.py:bump_fee

Log applicativo

Il registro di audit è nel database e si vede da /admin; il log applicativo è un file di testo e contiene molto di più (errori, riconnessioni, attese), ma va letto sul server.

  • [AUD-33] All'importazione di app.main, setup_logging aggiunge al logger radice (livello INFO) un RotatingFileHandler su logs/app.log, relativo alla radice del progetto: 10 MB per file, 5 file di rotazione (app.log.1 … app.log.5), formato data ora LIVELLO [logger] messaggio. Lo stesso handler è aggiunto a uvicorn.error e uvicorn.access, che non propagano al radice. Nel container la radice del progetto è /app, e /app/logs è montato su ./data/logs dell'host. Codice: app/logging_config.py:setup_logging, docker-compose.yml

  • [AUD-34] Il logger radice ha solo l'handler su file: i messaggi dei moduli dell'applicazione (scheduler, listener, riconciliatori) finiscono in data/logs/app.log e non sull'output del container. docker compose logs app mostra solo ciò che uvicorn scrive con la propria configurazione (avvio, richieste) e l'output di avvio del container (controllo della chiave, migrazioni Alembic). Codice: app/logging_config.py:setup_logging

  • [AUD-35] Un'eccezione non gestita in una richiesta HTTP viene registrata per intero nel log (Unhandled error on <metodo> <percorso>) e al client arriva solo un errore generico internal_error con stato 500, senza il testo dell'eccezione. Codice: app/main.py:log_unhandled_exception

  • [AUD-36] Le richieste di polling delle due interfacce (per esempio /rounds/current ogni 15 secondi per ogni pagina aperta, più un aggiornamento a ogni evento SSE) vengono tutte registrate da uvicorn.access. Con 60 MB di spazio complessivo, la rotazione può cancellare in poche ore o giorni i messaggi più vecchi: il log non è un archivio, il registro di audit sì. Codice: app/logging_config.py:setup_logging

Messaggi di log che contano

Messaggi non accompagnati da una riga di audit ma utili in esercizio. Il nome tra parentesi quadre nel log è il modulo (app.rounds.scheduler, ecc.).

Livello Modulo Messaggio (inizio) Significato
WARNING app.electrum.listener all N Electrum server(s) unreachable, retrying in Ns Tutti i server provati hanno fallito; attesa da 1 a 30s prima del giro successivo
ERROR (con traccia) app.electrum.listener Electrum session on … failed Errore nell'aprire la sessione: connessione, primo header, primo header rifiutato
INFO app.electrum.listener Electrum connected to … / Electrum connection to … ended, reconnecting Connessione aperta / chiusa normalmente
WARNING app.electrum.listener Electrum session on … ending: … Motivo della fine di una sessione già aperta (ping scaduto, socket chiuso, header successivo rifiutato con HeaderValidationError)
WARNING app.electrum.listener ignoring Electrum header at height … / ignoring a competing header … Header scartato senza chiudere la sessione (senza hex, più basso, concorrente)
WARNING app.electrum.listener could not corroborate … with any of N other configured server(s) Nessun altro server ha risposto: la corroborazione fallisce
WARNING app.electrum.listener N new UTXO(s) for user_id=… not corroborated by the other servers Deposito non ancora accreditato; si riprova al prossimo aggiornamento
WARNING app.electrum.listener N UTXO(s) spent outside the platform for user_id=… Accompagna utxo_spent_externally
WARNING app.electrum.listener could not subscribe a newly-registered user's address / resubscribing all users failed partway through Sottoscrizione persa; la recupera DepositReconciler entro 300s
ERROR (con traccia) app.electrum.listener failed to resubscribe user_id=… Un singolo utente non risottoscritto alla riconnessione
WARNING app.deposits.service listunspent for user_id=… returned no entries at all … Risposta vuota per un indirizzo con fondi: ignorata come incompleta
ERROR (con traccia) app.deposits.reconcile deposit reconciliation failed for user_id=… Il controllo periodico di un utente è fallito
ERROR (con traccia) app.rounds.scheduler round scheduler tick failed Eccezione in un ciclo dello scheduler. Se avviene durante drawing, il round resta bloccato (vedi operativita.md)
INFO app.rounds.scheduler round N: N bet(s) still in flight at close time, waiting Chiusura rinviata per scommesse ancora in volo
INFO app.rounds.scheduler round N: still waiting for a block past height … Estrazione in attesa, ogni 60s
ERROR app.rounds.scheduler round N: block … header … could not be corroborated … Accompagna draw_header_corroboration_failed
WARNING app.rounds.scheduler round N: chain tip was unknown at closing time … Accompagna draw_baseline_tip_unknown
ERROR app.rounds.scheduler round N payout blocked: … / payout deferred: not connected / payout failed: … / payout build failed / payout broadcast failed Accompagnano i vari payout_failed
INFO app.rounds.scheduler round N payout already in flight (pending_transaction N), skipping Pagamento già in volo. Mentre aspetta la conferma, ricompare a ogni ciclo (ogni 5s)
ERROR (con traccia) app.rounds.scheduler could not record the payout failure of round N Il payout_failed stesso non è stato salvato
ERROR app.rounds.service invariant violated: N rounds are active at once Più di un round attivo: non dovrebbe mai accadere
ERROR app.rounds.service could not open a round after 3 attempts Conflitti ripetuti all'apertura di un round
ERROR (con traccia) app.tx.broadcast could not bump pending_transaction N RBF impossibile (resto insufficiente o commissione già al massimo). Si ripete ogni 30s finché la transazione conferma o viene abbandonata
INFO app.tx.broadcast bumped <tipo> pending_transaction N: <vecchio> -> <nuovo> RBF riuscito: l'unica traccia di un aumento di commissione
WARNING app.tx.confirmation could not fetch history for pending_transaction … Controllo di conferma non riuscito, si riprova tra 10s
WARNING app.tx.reconcile could not check pending_transaction … against the chain Il riconciliatore non ha potuto decidere; non abbandona nulla
WARNING app.tx.reconcile abandoned <tipo> pending_transaction … Accompagna pending_tx_abandoned
ERROR app.tx.reconcile round N payout tx … vanished — round needs operator attention Pagamento abbandonato; il nuovo tentativo automatico parte da solo
ERROR app.auth.security stored password hash is unparseable … Hash di password corrotto nel database: quell'utente non può accedere
ERROR (con traccia) app.main Unhandled error on … Errore 500 su una richiesta ([AUD-35])