Only the duration and the cooldown (and, since B-73, the draw confirmations) were copied onto the round when it opened. The cost of a bet and the fee address were read live, so an edit from /admin in the middle of a round gave the players of that round different stakes for the same chance and redirected the commission of the round in progress. - `rounds.bet_amount_sats`, `rounds.fee_address` and `rounds.max_round_pool_sats` are copied from round_config in the transaction that opens the round (Alembic a7d3e9f15b48: plain add_column, existing rounds backfilled with the current configuration, i.e. what they were using; the expression indexes untouched). `place_bet` charges the round's cost, `_trigger_payout` pays the round's fee address (an empty one on the row still stops the payout, as before), `GET /rounds/current` advertises the round's cost while one is active and the configured one otherwise. The minimum withdrawal and the fee rate stay live, by design (ADR-20, ADR-10). - `round_config.max_round_pool_sats` (0 = none, otherwise >= the bet cost and <= 4e15, in /admin and PUT/GET /admin/config): a bet is refused with `round_full` (params also carry `max_round_pool_sats`) once the sum of the round's participant rows, in-flight ones included, has reached the round's cap, before anything moves; the pool can exceed it by one bet at most. A cap under the bet cost is a 422 `max_round_pool_below_bet_amount`. - PUT /admin/config's payability check (RND-40) now also judges a fee-rate change against the bet cost the round in progress opened with, which can be lower than the configured one. - `error.round_full` no longer says "maximum of N players" (it also fires for the pool cap): the seven translations are generic. Ambiguity resolved: the spec said the cap is snapshotted (RND-05, BET-20, ARC) in some places and "applies immediately, also to the round in progress" in the round_config table of modello-dati; the snapshot wins, with a column on `rounds` (modello-dati/api/guide-admin brought in line). Tests changed because the spec changed: the payout fixtures seed the round's own fee address, and the old "fee address missing" payout test now empties the round's address (clearing it in the configuration mid-round must not stop the payout). New test_round_snapshot.py: the copy at open, edits reaching only the next round, every bet of a round paying the same stake whether the cost is raised or lowered meanwhile, the payout following the round's address (and surviving it being cleared in the configuration), the cap (exactly at the cap is full, in-flight bets count, no cap, a mid-round edit of the cap does not apply, the refused player's coins untouched), plus the route, the admin bounds and the two 422s, the fee-rate check against the round's own cost, and the migration backfill. Mutation-checked: charging the live cost, paying the live address, not copying the cap and `>` instead of `>=` each fail tests. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
18 KiB
Guida admin
Come gestire la configurazione operativa di PLM Lottery. Presuppone che il server sia già avviato — vedi running-the-server.md. Per il funzionamento interno (stati del round, riconciliazione delle transazioni, connessione Electrum) vedi come-funziona.md.
Accesso
Il pannello admin è su https://<host>/admin — non è collegato da
nessun link nell'interfaccia utente (né in entrata né in uscita): ci si
arriva solo conoscendo l'URL. La pagina mostra solo un campo token finché non
accedi: non è protetta da login personale, ma da un token condiviso
(ADMIN_TOKEN, definito in .env).
Incolla il valore di ADMIN_TOKEN e premi "Accedi" (o Invio): se il token è
valido, si apre la dashboard con una navbar in alto e carica automaticamente
tutte le sezioni — nessun bottone "Carica" separato. Il token resta in
sessionStorage (si perde chiudendo la tab/il browser); "Esci" torna alla
sola schermata di login.
Sezioni della dashboard
- Parametri — configurazione operativa (vedi tabella sotto)
- Utenti — elenco utenti, saldo, accesso alla chiave privata, reset password
- Round — storico round: stato, vincitore, importi, txid di payout
- Transazioni pendenti — bet/payout/prelievi non ancora confermati, candidati al fee-bump RBF
- Audit log — eventi registrati dal sistema (config cambiata, bet, payout, accessi a chiavi private, ecc.; catalogo completo in audit-log.md)
- Segnalazioni bug — segnalazioni inviate da
/report-bug, da smistareopen→read→resolved
Nella navbar, a destra, una pillola mostra lo stato del round ("In attesa
del prossimo round", "Round aperto", oppure "Estrazione in corso", che copre
chiusura, estrazione e pagamento) e l'altezza dell'ultimo blocco noto,
aggiornate ogni 15 secondi da GET /rounds/current. Non indica se la
connessione Electrum è attiva né a quale server: un'altezza che non sale per
parecchi minuti è il segnale da guardare (procedura in
operativita.md, "Electrum irraggiungibile").
Parametri
Tutti i parametri operativi/di business sono nella sezione "Parametri",
salvati nel database — modificabili in qualsiasi momento, effetto immediato,
nessun riavvio del server necessario. Non esiste alcuna variabile
d'ambiente equivalente: .env contiene solo segreti e configurazione di
infrastruttura (chiave master, JWT, Electrum, token admin), non parametri di
business — quelli si toccano solo da qui.
| Campo | Significato |
|---|---|
| Fee address | L'indirizzo PLM su cui finisce il 30% di ogni round (fee); una modifica vale dal round successivo, mai per quello in corso. Obbligatorio: su un'istanza nuova è vuoto, e finché resta vuoto non si apre nessun round (il payout non sarebbe costruibile, quindi il round accetterebbe scommesse per poi restare bloccato con i soldi già nel montepremi). Il pannello lo segnala con un avviso in cima ai Parametri, e la pagina utente mostra un banner "lotteria non ancora pronta". Una volta impostato non si può più svuotare dal pannello: un valore vuoto non è un indirizzo plm1... valido e viene rifiutato. Attenzione: oggi il payout legge questo campo al momento in cui viene costruito, quindi cambiarlo mentre un round è in corso sposta anche la commissione di quel round sul nuovo indirizzo. |
| Bet amount (PLM) | Il costo fisso d'ingresso per round, dal round successivo: il round in corso tiene il costo con cui si è aperto, così tutti i suoi giocatori pagano la stessa posta. È anche l'importo minimo prelevabile, e questo vale subito: un prelievo sotto questa soglia viene rifiutato (i depositi non hanno un controllo minimo lato server). Un valore con cui un round non si potrebbe pagare (commissione di rete, riserva e soglia di polvere non coperte) viene rifiutato. |
| Durata round (secondi) | Quanto resta aperto un round prima di chiudersi ed estrarre il vincitore. Il taglio per le nuove giocate scatta esattamente allo scadere di questo tempo (verificato ad ogni bet, non dipende dal ciclo dello scheduler) — è un "semaforo giallo": nessuna nuova entrata, ma le bet già trasmesse prima dello scadere hanno comunque tempo di confermarsi prima che il round chiuda ed estragga. |
| Tetto al montepremi di un round (PLM) | 0 = nessun tetto. Con un tetto, una scommessa è rifiutata (round_full) quando la somma delle scommesse del round lo ha raggiunto; il montepremi può quindi superarlo al massimo di una scommessa. Non può essere minore del costo della scommessa. Vale dal round successivo. |
| Pausa tra un round e il successivo (secondi) | Cooldown dopo la chiusura di un round, prima che il successivo si apra — dà tempo ai giocatori di vedere l'esito. |
| Durata animazione estrazione (secondi) | Tempo minimo per cui la dashboard di ogni utente mostra l'animazione "Estrazione in corso" dopo la chiusura del round, prima di rivelare il vincitore. È solo un minimo: il processo reale aspetta più blocchi in sequenza (conferma dell'ultima bet in sospeso, il blocco dell'estrazione con le sue conferme, conferma del payout — un blocco ogni 2 minuti in media), quindi l'animazione può durare più a lungo di questo valore, mai meno. |
| Conferme per accreditare un deposito | Da 1 a 100 (predefinito 6): dopo quante conferme un deposito arrivato all'indirizzo di un utente viene accreditato e diventa spendibile. Un deposito è l'unico ingresso che un utente può spendere due volte con una catena privata, perciò il valore non va abbassato senza motivo. Vale subito per gli accrediti successivi; le uscite di mining (coinbase) ne richiedono comunque 120, e le transazioni costruite dalla piattaforma (resto di una bet, vincite) una sola. |
| Conferme sul blocco di estrazione | Da 1 a 20 (predefinito 3): quanti blocchi deve avere la catena a partire dal blocco di estrazione prima che il suo hash scelga il vincitore. Più conferme rendono l'estrazione più robusta a una riorganizzazione della catena e allungano l'attesa di circa 2 minuti per blocco. Si applica dal round successivo. |
| Fee rate di rete (sat/vB) | Fee per byte usata per costruire bet, payout e prelievi. |
| Timeout prima del fee-bump RBF (secondi) | Dopo quanto tempo senza conferma una transazione viene ritrasmessa con fee più alta. |
Durata e cooldown si applicano dal round successivo, non a quello già in corso: ogni round si porta dietro i valori con cui è stato aperto, così abbassare la durata mentre un round è a metà non lo chiude di colpo, e alzarla non sposta il countdown che i giocatori stanno già guardando. Tutti gli altri parametri sono invece a effetto immediato, anche sul round in corso: un nuovo bet amount vale per le scommesse successive (il montepremi annunciato resta calcolato su quanto è stato davvero puntato), fee rate e RBF timeout valgono per le prossime transazioni costruite o accelerate, compreso il payout del round in corso, e il fee address vale per il prossimo payout costruito (vedi sopra).
Tutti gli importi in PLM vengono convertiti in sats (1 PLM = 100.000.000 sats) solo nella chiamata API — il backend lavora sempre in sats.
Su un'istanza nuova (mai avviata), questi campi partono con dei default
hardcoded nel codice (RoundConfig in app/db/models.py: bet 10 PLM, round
10 minuti, cooldown 30s, animazione estrazione 20s, fee 1
sat/vB, RBF timeout 900s) — vanno
comunque rivisti e confermati dal pannello prima del primo utilizzo reale.
Manutenzione (pausa/ripresa lotteria)
In cima alla sezione "Parametri" c'è una card "Manutenzione" con un pulsante per fermare l'apertura di nuovi round — utile per intervenire sul server (aggiornamenti, riavvii) senza lasciare gli utenti a metà di un round o sorprenderli con un'interruzione improvvisa.
- "Interrompi dopo questo round": il round eventualmente in corso viene completato normalmente — chiude, estrae il vincitore da un blocco confermato, e paga il 70/30 come sempre. Solo l'apertura del round successivo viene sospesa. La pausa non ferma nemmeno le scommesse e i prelievi sul round ancora aperto: il round prosegue fino alla sua scadenza. Gli utenti vedono un avviso di manutenzione sulla loro dashboard (e sulla home, anche da sloggati) finché la lotteria resta in pausa.
- "Riprendi lotteria": annulla la pausa — al prossimo giro dello scheduler (ogni 5 secondi) un nuovo round si apre normalmente (rispettando comunque il cooldown se il precedente si è appena chiuso).
Ogni pausa/ripresa viene registrata nell'audit log (lottery_paused /
lottery_resumed), ma — come per il resto del pannello — non registra
quale operatore l'ha premuta (token condiviso, vedi
limiti-noti.md).
Prima di un riavvio o di un deploy la pausa da sola non basta: bisogna anche
aspettare che il round in corso sia closed, perché oggi un riavvio durante
l'estrazione blocca il round. La procedura è in
running-the-server.md e, completa, in
operativita.md (OPS-10).
Chi paga il fee-bump RBF? Quando una bet, un payout o un prelievo resta
troppo a lungo senza conferma (oltre il "Timeout prima del fee-bump RBF"), il
sistema lo ritrasmette con una fee più alta. Il costo aggiuntivo lo assorbe
sempre chi ha originato la transazione, non il destinatario: per bet e
prelievi è l'utente stesso (gli torna un resto più piccolo), per i payout è
il pool (si riduce il resto che torna all'indirizzo pool) — la quota del
vincitore e quella delle fee, già fissate, non vengono mai toccate. Se non
c'è un resto abbastanza grande da assorbire l'aumento, il bump fallisce (nel
log could not bump pending_transaction N, ripetuto ogni 30 secondi) e la
transazione resta in attesa. Per i payout è il caso normale: il pool di
un round contiene di solito esattamente le scommesse del round e il payout le
spende tutte, quindi non ha resto e non si può accelerare. Una transazione
ferma nel mempool non viene nemmeno abbandonata, perché il server la conosce:
se è un payout, il round resta in paying_out e nessun nuovo round si apre
finché non conferma. Cosa fare è in operativita.md;
il funzionamento in
transazioni-e-riconciliazione.md.
Round
La sezione "Round" mostra lo storico (GET /admin/rounds, ultimi 50 per
default): id, stato, orario di apertura, vincitore (username), importo del
pool, importo vinto, importo di fee, txid del payout — tutti in PLM salvo il
txid.
Transazioni pendenti
GET /admin/pending-transactions elenca le ultime 50 transazioni tracciate
(bet, payout e prelievi), dalla più recente, in qualunque stato: non solo
quelle ancora building o pending, ma anche quelle già confirmed o
failed — per sapere cosa è davvero in sospeso si guarda la colonna Stato.
Per ogni riga: id, tipo, stato, txid corrente, fee rate usata, numero di
tentativi (si incrementa a ogni bump RBF) e orario del primo invio (non
cambia con i bump). Una riga che resta pending a lungo, con il numero di
tentativi che sale, indica una transazione in difficoltà; una che resta
pending con il numero di tentativi fermo è spesso un bump impossibile (vedi
sopra "Chi paga il fee-bump RBF?").
Audit log
GET /admin/audit-log elenca gli ultimi 200 eventi registrati dal sistema
(tipo evento, payload JSON, utente/round coinvolti, timestamp): bet
piazzate, payout inviati, round chiusi senza partecipanti, configurazione
modificata, accessi alle chiavi private, ecc. È il primo posto da controllare
per ricostruire cosa è successo dopo un problema. La sezione non ha filtri né
ricerca, e l'endpoint accetta solo limit (al massimo 500): gli eventi più
vecchi si leggono solo direttamente dal database. Non tutto viene
registrato: per esempio registrazioni, cambi password fatti dagli utenti e
bump RBF non lasciano righe (elenco in audit-log.md).
Eventi a cui vale la pena prestare attenzione:
| Evento | Significato |
|---|---|
config_updated |
Un parametro è stato modificato; il payload contiene valore precedente e nuovo per ogni campo cambiato. |
bet_broadcast_failed / withdrawal_broadcast_failed |
L'invio della transazione non è riuscito: gli UTXO sono stati liberati e il saldo dell'utente è tornato come prima. Oggi l'evento viene scritto per qualunque errore d'invio, anche un timeout o una connessione caduta dopo l'invio, casi in cui la transazione potrebbe essere partita lo stesso: se il reason del payload non è un rifiuto esplicito della rete, controllare il txid con un explorer. |
pending_tx_abandoned |
Il sistema ha dichiarato persa una transazione: UTXO liberati, bet rimossa o prelievo segnato failed. Oggi basta che il server Electrum attivo non la conosca (dopo 120 secondi per una riga building, 6 ore per una pending): dopo un riavvio o un cambio di server può essere un falso allarme, con la transazione ancora viva o già confermata. Controllare sempre il txid del payload con un explorer — procedura in operativita.md ("Transazione abbandonata"). |
pending_tx_recovered |
Una transazione che si credeva incompleta è invece finita in catena (tipicamente dopo un riavvio a metà invio) e il sistema l'ha ripresa da sé. |
bug_report_status_changed |
Un admin ha cambiato lo stato di una segnalazione (payload: report_id, stato precedente e nuovo). Con un token admin unico e condiviso, questa riga è l'unica traccia di chi tocca le segnalazioni. |
payout_failed |
Un tentativo di payout di un round non è partito; il campo reason dice perché. Il round resta in paying_out e lo scheduler riprova da solo al massimo una volta ogni 60 secondi. Se l'evento si ripete, controlla fee_address, il saldo dell'indirizzo pool e la connessione Electrum (procedure in operativita.md). |
Alternative all'interfaccia grafica
Le stesse operazioni si possono fare da terminale o da Swagger UI
(https://<host>/docs, sezione admin — disponibile solo se ENABLE_API_DOCS=true
è impostato in .env, disattivata di default perché espone l'intera API),
sempre passando ADMIN_TOKEN nell'header X-Admin-Token:
# leggere la configurazione
curl https://<host>/admin/config -H "X-Admin-Token: <ADMIN_TOKEN>"
# aggiornarla (importi in sats: 10 PLM = 1000000000; solo i campi passati vengono cambiati)
curl -X PUT https://<host>/admin/config \
-H "X-Admin-Token: <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"fee_address": "plm1q...", "bet_amount_sats": 1000000000, "round_duration_seconds": 600}'
I valori vengono validati: fee_address deve essere un indirizzo bech32 PLM
valido (plm1...) e i parametri numerici hanno limiti di buon senso
(fee_rate_sat_vb almeno 1, durata round almeno 30s, ecc.). Un valore fuori
range viene rifiutato con un errore 422 e la configurazione resta invariata.
Il controllo su fee_address è deliberatamente severo: un indirizzo di
un'altra catena (per esempio bc1...) sarebbe formalmente valido come witness
program, e il 30% di commissione di ogni round finirebbe su uno script di cui
nessuno ha la chiave.
Utenti, chiave privata e reset password
La card "Utenti" elenca id, username, indirizzo e saldo di ogni utente
registrato. Il bottone "Mostra" su ogni riga rivela la chiave privata (WIF)
di quell'utente, dietro conferma esplicita — serve per interventi manuali
(es. restituire fondi bloccati). Ogni visualizzazione viene registrata
nell'audit log (admin_privkey_accessed). Questo non introduce una nuova
falla: il server è già custodial, la chiave master da cui derivano tutte le
chiavi utente vive sul server — questo pannello espone solo qualcosa che
l'operatore può già fare via script.
Il bottone "Reset" nella colonna "Password" genera una nuova password
casuale per l'utente e sovrascrive quella esistente — mostrata una sola
volta nel pannello, così puoi comunicarla a chi ti ha chiesto aiuto perché
l'ha dimenticata. Non è un "recupero": le password sono salvate solo come
hash Argon2 (app/auth/security.py), quindi quella vecchia non è mai
recuperabile né per l'admin né per il codice stesso — l'unica opzione è
sempre sostituirla con una nuova. Anche questa azione è audit-loggata
(admin_password_reset) e non esiste alcun flusso self-service equivalente
per l'utente: solo un admin col token può farlo.
Limiti noti
- Il token è unico e condiviso: non c'è identità per singolo admin né audit
di chi ha cambiato cosa (oltre alla tabella
audit_loggenerica). - Nessun rate limiting sugli endpoint admin, né su bet e prelievo. Solo il login (tentativi falliti) e la registrazione (5 account/ora per IP) sono limitati — vedi sicurezza.md.
- Se un payout fallisce (es. Electrum disconnesso, UTXO insufficienti), il
round resta in
paying_oute il payout viene ritentato ogni 60 secondi; alcuni motivi però non si risolvono da soli (vedi operativita.md). - Un riavvio mentre un round è in estrazione (
drawing) lo blocca: prima di ogni riavvio mettere in pausa e aspettareclosed(vedi "Manutenzione" sopra). - Elenco completo dei limiti accettati: limiti-noti.md.
Per l'elenco completo dei gap noti vedi la sezione "Known gaps / TODO" in
CLAUDE.md.
Cosa cambierà
La revisione del 2026-09-28 ha deciso delle correzioni non ancora implementate; quelle che cambiano ciò che si vede da questo pannello sono (dettaglio e ordine in piano-correzioni.md):
- La pausa avrà un motivo. Oltre a quella manuale, il sistema metterà in pausa la lotteria da solo per una riorganizzazione profonda della catena, per un payout bloccato o per una transazione data per persa e poi confermata; il pannello mostrerà il motivo, e "Riprendi lotteria" resterà l'unico modo di uscirne, dopo le verifiche di operativita.md (OPS-30).
- Payout bloccato. Se la scommessa del vincitore sparisce dalla catena,
il payout non partirà (evento
payout_blocked) e il round resterà inpaying_outfino alla ripresa; i partecipanti la cui scommessa è sparita verranno esclusi dal montepremi (participant_reorged_out). - Stato Electrum. Il pannello mostrerà se la connessione è attiva, il server in uso e da quanto non arriva un blocco.
- Audit log consultabile per intero, con filtri per tipo di evento,
round, utente e intervallo di tempo, e a pagine; nuovi eventi come
user_registered,password_changed,rbf_bump_failed,closing_stalledechain_reorg_detected(catalogo in audit-log.md).