Files
plm-lottery/docs/adr/ADR-07-ogni-decisione-sposta-denaro-base-risposta.md
T
davideandClaude Sonnet 5.5 13cb66cc06 Require a real Electrum quorum and allow certificate pinning (B-84, ELC-01, ELC-03, ELC-14, ELC-19, ARC-20, OPS-12)
An empty ELECTRUM_FALLBACK_SERVERS (the .env.example default) silently made every
corroboration pass, and with fallbacks the majority was taken over whoever
answered, so one reachable fallback decided alone. TLS certificates were never
checked.

- _corroborate_majority passes only with at least 2 answers (MIN_QUORUM_RESPONSES)
  and a strict majority of the answers; no other server configured, or fewer than
  2 answering, fails. ALLOW_NO_QUORUM=true keeps the old reduced rule.
- config.validate_quorum_servers, called from the lifespan after parse_endpoints,
  refuses to start with fewer than 2 fallback servers unless ALLOW_NO_QUORUM=true
  (which logs a warning at every start).
- Optional pinning: `host:port[:mode]:pin=<sha256>` per fallback and
  ELECTRUM_CERT_PIN for the primary; ElectrumClient compares the SHA-256 of the
  DER certificate after the TLS handshake and a mismatch fails like an unreachable
  server. A pin on a plaintext entry is a startup error.
- .env.example now ships the four bootstrap servers and documents both new
  variables; docs/setup.md, spec, storico and BUGS.md updated.
- Tests changed because the spec changed: the two "no other servers configured ->
  True" corroboration tests now assert that outcome only under allow_no_quorum.

Note: a dev .env with fewer than 2 fallback servers will now refuse to start.

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

38 lines
2.6 KiB
Markdown

# ADR-07 — Ogni decisione che sposta denaro in base a una risposta di rete è corroborata da un quorum degli altri server configurati
- **Stato:** Accettata — implementazione da completare (B-83)
- **Area:** Conferme e fiducia nella rete
- **Fonte di verità per:** la scelta di progetto descritta qui sotto; i requisiti (`PREFISSO-NN`) che la attuano stanno nei documenti di dominio, indicati nelle *Conseguenze*.
## Decisione
**Ogni decisione che sposta denaro in base a una risposta di
rete è corroborata da un quorum degli altri server configurati**: il blocco
che semina l'estrazione, una moneta data per spesa all'esterno, un nuovo
deposito da accreditare, il ripristino di una moneta, la conferma di una
transazione (con prova merkle contro un header che possediamo), l'abbandono
di una transazione, le monete del round verificate prima del pagamento. Si
interrogano solo gli *altri* server, mai quello attivo (che è ciò che un
attaccante controllerebbe); servono **almeno 2 risposte** e la maggioranza
stretta di chi risponde. Il server rifiuta di partire con meno di 2 server
di riserva, salvo `ALLOW_NO_QUORUM=true` (solo sviluppo). Se rispondono meno
di 2 server, la corroborazione fallisce.
- *Motivazione:* un solo server ostile non deve poter scegliere il
vincitore (B-28), azzerare un saldo (B-29) o gonfiarlo (B-59), né far
credere confermato un pagamento mai trasmesso (B-83). I saldi si muovono in
entrambe le direzioni, quindi entrambe richiedono il quorum. Con 0 o 1
server di riserva la «maggioranza» è una sola voce, o nessuna (B-84).
- *Alternative scartate:* quorum facoltativo, che passa sempre con la lista
vuota (la regola precedente: il default di `.env.example` spegneva ogni
difesa senza avvisare).
- *Conseguenze:* una corroborazione fallita **ritarda**, non perde e non
libera fondi ([ADR-51](ADR-51-dubbio-liberano-mai-fondi.md)): il deposito è riproposto al refresh successivo,
l'estrazione richiede di nuovo lo stesso blocco. Il quorum resta fra server
scelti dall'operatore ([LIM-11]). Regole in
[connessione-electrum.md](../connessione-electrum.md#5-il-quorum) (B-83, B-84).
*Codice:* `app/electrum/listener.py:ElectrumListener._corroborate_majority` · `app/electrum/listener.py:ElectrumListener.corroborate_header` · `app/electrum/listener.py:ElectrumListener.corroborate_utxo_spent` · `app/electrum/listener.py:ElectrumListener.corroborate_utxo_credit` · `app/main.py:lifespan` ·
*Test:* `tests/unit/test_electrum_listener.py`
*Stato:* da implementare (B-83) · *Oggi:* conferme, ripristini e abbandoni
si decidono sul solo server attivo.