Files
plm-lottery/docs/come-funziona.md
T
davideandClaude Sonnet 5.5 db24287ea5 Make the draw a commit-reveal at a fixed, confirmed, quorum-agreed height with a public proof (B-73, RND-14 … RND-20, RND-33 … RND-37, DAT-61 … DAT-66, API-25, API-45, AUD-24 … AUD-27, AUD-37, ELC-15, SEC-56, ADR-24, ADR-25)
The seed was the hash of the first block seen after the close, so a miner holding tickets
could withhold blocks until one made them win, the active Electrum server could pick which
real block was used by not announcing another (a failed corroboration even moved the draw to
the next block), and the participant order was the server-only `broadcast_at` with no
tie-break, so nobody could recompute the result.

- Open: every round gets a 32-byte `server_seed`, stored only Fernet-encrypted with the
  master-xprv key (`server_seed_enc`), with its published `server_seed_hash`;
  `draw_confirmations` is copied from round_config. The plaintext column is written only by
  the reveal.
- Entering `drawing` (one transaction, `draw_committed` audit row): participants ordered by
  bet txid, each `draw_position`, `participants_hash` (sha256 of `txid:vout:address\n`
  lines; new `bet_vout`), `draw_height = tip_at_close + 1`. A tip of 0 keeps the round
  `closing` (`draw_baseline_tip_unknown`, once per round). All persisted, so a resumed draw
  reads rather than recomputes (the `_tick` branch itself is B-81).
- `_complete_draw` / `_wait_for_draw_block` replace `_wait_for_next_block`: wait for
  `draw_confirmations` blocks from `draw_height`, then `ElectrumListener.agreed_block_hash`
  asks every server, the active one included, for the header at that exact height (valid
  work, >= 2 other answers, strict majority of all); no agreement means the same height is
  asked again and `draw_header_corroboration_failed` is written once per distinct reported
  hash. `draw_stalled` now counts from the last tip advance, or from when the quorum started
  to fail. Seed = sha256(server_seed || block_hash || participants_hash), index = seed mod n.
- The winner, the seed and the reveal (`server_seed`, `winner_drawn` payload) are written in
  one transaction; a round closing with no participants reveals too. A round opened before
  this change (no commitment) is given a secret as it enters `drawing`.
- `GET /rounds/{id}/proof` (public, 404 `round_not_found`, translated in all 7 languages) and
  the new fields on `GET /rounds/current`; `draw_confirmations` in `/admin/config` and the
  /admin form; `server_seed_hash`/`draw_height` in `/admin/rounds`, never the secret.
- Alembic d3e8b15a7c42 (plain add_column / create_index, expression indexes untouched).
- Docs: the B-73 `*Stato:*`/`*Oggi:*` lines are gone, guides and CLAUDE.md describe the new
  draw. Ambiguities resolved in api.md: the proof's participants also carry `amount_sats`
  (RND-37 lists it); `seed` is decimal in the proof and hex in the audit row, as specified.

Tests changed because the spec changed: ElectrumListener.corroborate_header (asked only the
other servers about the block the active one announced) is replaced by agreed_block_hash and
its quorum tests ported; draw_winner takes the secret and the list fingerprint; the four
_wait_for_next_block scheduler tests are replaced by _wait_for_draw_block ones (fixed height,
confirmations, same-height retry, stall clock). New: test_draw_proof.py recomputes the winner
independently from the public proof, checks the plaintext secret is nowhere in the database,
the API or the log before the reveal, and that a second pass over a `drawing` round yields
the same winner. Mutation-checked: ordering by broadcast_at, an early reveal and asking the
tip instead of the draw height each fail tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 16:29:09 +02:00

11 KiB
Raw Blame History

Come funziona PLM Lottery

Panoramica del funzionamento della piattaforma (l'indice completo della documentazione è README.md). È il punto di partenza: ogni sezione rimanda al documento che la approfondisce.

Documento Cosa spiega
registrazione-e-depositi.md Come nasce un account, come arrivano i fondi, come si calcola il saldo mostrato
scommesse-e-prelievi.md Come si piazza una scommessa e come si ritirano i fondi
ciclo-di-vita-del-round.md Apertura, chiusura, estrazione del vincitore, pagamento
transazioni-e-riconciliazione.md Come ogni movimento di denaro viene registrato, inviato, accelerato (RBF) e riconciliato con la catena
connessione-electrum.md Come il server parla con la rete PLM e perché non si fida di un solo server
architettura.md Struttura del codice, processi in background, configurazione, frontend, traduzioni, test
modello-dati.md Tabelle del database, campi, stati, vincoli, migrazioni
api.md Tutti gli endpoint HTTP, formato delle risposte, catalogo dei codici di errore
sicurezza.md Modello di fiducia, chiavi e segreti, autenticazione, limiti sui tentativi, header HTTP
audit-log.md Catalogo degli eventi del registro di audit
operativita.md Backup, riavvii, procedure per gli incidenti
adr/ Le decisioni di progetto con le loro motivazioni, una per file (ADR)
limiti-noti.md Limiti noti accettati
storico-bug.md Tutti i bug B-nn trovati e corretti, con commit e test
piano-correzioni.md Decisioni della revisione del 2026-09-28 e ordine in cui implementare le correzioni ancora aperte

Questi documenti sono la specifica del software: ogni regola ha un identificativo stabile (ad esempio RND-11) con il riferimento al codice che la implementa. Come si usano per modificare il software è spiegato in processo-di-modifica.md.

Dalla revisione del 2026-09-28 la specifica descrive il comportamento atteso, che in alcuni punti è più avanti del codice. I requisiti che il codice non soddisfa ancora sono marcati *Stato:* da implementare (B-nn) e dicono in una riga cosa fa il codice oggi (git grep "da implementare" li elenca tutti); anche questa panoramica descrive il comportamento atteso. Le decisioni prese e l'ordine di implementazione sono in piano-correzioni.md, il dettaglio tecnico di ogni voce in BUGS.md.

Per installazione e avvio vedi setup.md e running-the-server.md; per l'uso delle interfacce guida-utente.md e guida-admin.md.

In una frase

Gli utenti depositano PLM su un indirizzo personale custodito dal server, comprano un biglietto a costo fisso per il round in corso, e allo scadere del round un seme che combina un segreto su cui il server si è impegnato all'apertura, l'hash di un blocco successivo alla chiusura e l'elenco dei partecipanti sceglie un vincitore, che riceve il 70% del montepremi; il 30% va alla piattaforma.

Scelte di fondo

  • Custodiale. Le chiavi sono generate e tenute dal server (una master xprv cifrata a riposo). L'utente non controlla nulla finché non preleva.
  • Un indirizzo per utente, per sempre. Lo stesso indirizzo serve per i depositi, riceve le vincite e riceve il resto di ogni scommessa e prelievo.
  • Nessun nodo completo. Tutto l'accesso alla rete passa dal protocollo Electrum (vedi connessione-electrum.md).
  • 6 conferme per i depositi, 1 per tutto il resto. Il deposito è l'unico ingresso che un utente può spendere due volte, perché scommesse, pagamenti e prelievi li firma solo il server; le uscite coinbase richiedono 120 conferme (vedi registrazione-e-depositi.md).
  • Estrazione con commit-reveal. All'apertura il server si impegna su un segreto pubblicandone l'impronta; alla chiusura si fissano elenco dei partecipanti e altezza del blocco; il segreto viene rivelato dopo l'estrazione. Chi produce i blocchi non può calcolare il vincitore e l'operatore non può cambiare il segreto; chiunque può verificare il risultato con la prova pubblica (vedi ciclo-di-vita-del-round.md).
  • Un indirizzo del montepremi per ogni round, così soldi e cronologia di un round restano separati da quelli degli altri.
  • Nel dubbio non si liberano i fondi. Una transazione viene annullata solo con una prova, mai per un timeout o per il silenzio di un solo server (vedi transazioni-e-riconciliazione.md).
  • Parametri operativi nel database, modificabili a caldo da /admin (costo della scommessa, durata del round, commissioni…). Nel file .env restano solo segreti e collegamenti infrastrutturali.

Le cinque fasi

Diagramma del comportamento atteso:

flowchart LR
    subgraph REG["1 · Registrazione"]
        direction TB
        A1["Username + password"] --> A2["Il server deriva un indirizzo<br/>dedicato e permanente"]
    end

    subgraph DEP["2 · Deposito"]
        direction TB
        B1["L'utente invia PLM<br/>al proprio indirizzo"] --> B2{"6 conferme e<br/>corroborato dagli<br/>altri server?"}
        B2 -- "No" --> B1b["Si riprova al prossimo<br/>aggiornamento"] --> B2
        B2 -- "Sì" --> B3["Saldo accreditato"]
    end

    subgraph PLAY["3 · Scommessa"]
        direction TB
        C1{"Saldo sufficiente,<br/>nessuna scommessa attiva,<br/>round aperto e non pieno?"}
        C1 -- "Sì" --> C2["Transazione: indirizzo utente →<br/>montepremi del round, resto all'utente"]
        C2 --> C3{"Confermata?"}
        C3 -- "No, troppo tempo" --> C4["Commissione aumentata<br/>(RBF) e reinvio"] --> C3
        C3 -- "Sì" --> C5["Partecipante del round"]
    end

    subgraph DRAW["4 · Round ed estrazione"]
        direction TB
        D0["Apertura: indirizzo del montepremi<br/>del round, impronta del segreto pubblicata"] --> D1["Scadenza del round"]
        D1 --> D2["Si attendono le scommesse<br/>già in volo"]
        D2 --> D3["Si fissano partecipanti<br/>e altezza del blocco"]
        D3 --> D3b["Blocco con 3 conferme, header<br/>concordato dal quorum:<br/>segreto + blocco + partecipanti<br/>scelgono il vincitore"]
        D3b --> D3c["Segreto rivelato,<br/>prova pubblica"]
        D3c --> D3d{"Scommesse ancora<br/>sulla catena?"}
        D3d -- "Sì (quelle sparite<br/>sono escluse)" --> D4["70% al vincitore, 30% alla piattaforma<br/>meno la riserva per l'RBF"]
        D3d -- "Sparita quella<br/>del vincitore" --> D5["Pagamento bloccato,<br/>lotteria in pausa"]
    end

    subgraph WITHDRAW["5 · Prelievo"]
        direction TB
        E1["Indirizzo esterno + importo"] --> E2["Transazione: indirizzo utente →<br/>esterno, resto all'utente"]
        E2 --> E3{"Confermata?"}
        E3 -- "No, troppo tempo" --> E4["RBF e reinvio"] --> E3
        E3 -- "Sì" --> E5["Saldo aggiornato"]
    end

    A2 --> B1
    B3 --> C1
    B3 --> E1
    C5 --> D1
    D4 -.->|"round successivo"| C1
  1. Registrazione — registrazione-e-depositi.md
  2. Deposito — registrazione-e-depositi.md
  3. Scommessa — scommesse-e-prelievi.md
  4. Round ed estrazione — ciclo-di-vita-del-round.md
  5. Prelievo — scommesse-e-prelievi.md

Componenti

Un solo processo Python (FastAPI) dietro Caddy, con un database SQLite. Dentro il processo girano, oltre all'API HTTP, sei attività in background avviate all'avvio del server:

Attività Ogni Cosa fa
ElectrumListener connessione continua, ping ogni 60s L'unica connessione alla rete: riceve i nuovi blocchi, osserva gli indirizzi di tutti gli utenti, accredita i depositi
RoundScheduler 5s Apre e chiude i round, esegue l'estrazione, avvia e riprova i pagamenti
ConfirmationPoller 10s Porta le transazioni da "in attesa" a "confermate"
RbfBumper 30s Aumenta la commissione di ciò che non conferma entro il tempo limite
PendingTransactionReconciler all'avvio, poi 120s Confronta con la catena ogni transazione rimasta a metà
DepositReconciler 300s Ricontrolla tutti gli indirizzi, nel caso una notifica sia andata persa

La cadenza di queste attività è ciò che determina quanto in fretta il sistema si ripara da solo dopo un problema.

Le due interfacce web sono pagine statiche servite dallo stesso server, senza build né framework:

  • / — l'interfaccia per i giocatori (registrazione, deposito, scommessa, prelievo, profilo, stato del round), tradotta in 7 lingue.
  • /admin — il pannello operatore, protetto da un token condiviso, in italiano.

Entrambe interrogano periodicamente l'API e in più ascoltano un canale SSE (GET /rounds/stream): un semplice segnale "è cambiato qualcosa, ricarica" inviato a ogni evento rilevante (round aperto o cambiato di stato, scommessa o prelievo inviato, transazione confermata, deposito accreditato, nuovo blocco). Se il canale cade, le pagine continuano a funzionare con il solo polling.

Parametri principali

Parametro Valore predefinito Modificabile
Costo della scommessa 10 PLM da /admin, vale dal round successivo (B-95)
Ripartizione del montepremi 70% vincitore / 30% piattaforma no, è nel codice
Durata del round 600s da /admin, vale dal round successivo
Pausa tra un round e l'altro 30s da /admin, vale dal round successivo
Commissione di rete 1 sat/vB da /admin
Tempo prima di un RBF 900s da /admin
Prelievo minimo pari al costo della scommessa segue il costo
Deposito minimo nessuno —
Partecipanti massimi per round 400 no, è nel codice
Tetto al montepremi del round (max_round_pool_sats) 0 = nessun tetto (B-95) da /admin, vale dal round successivo
Conferme per i depositi (deposit_confirmations) 6; 120 per le uscite coinbase (B-82) da /admin; le 120 coinbase no, sono nel codice
Conferme sul blocco di estrazione (draw_confirmations) 3 da /admin, vale dal round successivo
Conferme per scommesse, pagamenti, prelievi 1 no, è nel codice
Indirizzo del montepremi uno per round, m/84'/746'/0'/2/<id_round> (B-80) no, è nel codice
Riserva per l'RBF sul pagamento max(294 sat, dimensione stimata × 10 sat/vB), tolta dalla quota della piattaforma, verso m/84'/746'/0'/1/0 (B-79) no, è nel codice

Un B-nn nella tabella indica un parametro del comportamento atteso non ancora implementato (vedi piano-correzioni.md).