Align docs with the code: stale columns, B-92 health threshold, duplicates
- modello-dati: drop 'da implementare' from fields B-73/B-80/B-82/B-85 shipped and the stale B-76 note; limiti-noti: remove a duplicated paragraph - /health/ready threshold 360s -> 1800s in the spec (B-92, not yet built): with 120s blocks 360s misfires ~5% of the time (e^-3) - storico-bug/CLAUDE.md/README: register B-99, B-100, test count 698 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cef05943f9
commit
264441f5d6
6 files changed
+11
-10
No files matched your search
@@ -142,7 +142,7 @@ digest-pinned base images, the Fernet key mounted as a file rather than an env v
|
||||
returns only the latest 500 rows unfiltered; registration writes no audit row; the
|
||||
access log records Caddy's IP; app logs go only to the file.
|
||||
|
||||
Fix: `GET /health/ready` (Electrum connected, tip age < 360s, scheduler heartbeat <
|
||||
Fix: `GET /health/ready` (Electrum connected, tip age < 1800s, scheduler heartbeat <
|
||||
30s) used as the container healthcheck; Electrum status in `/admin`; audit filters by
|
||||
event type, round, user and time, with pagination; `user_registered` audit row with
|
||||
index and address; uvicorn proxy headers from Caddy only; logs also to stdout.
|
||||
|
||||
@@ -8,7 +8,7 @@ The user communicates in Italian in chat — reply to them in Italian. Everythin
|
||||
|
||||
## Project status
|
||||
|
||||
All 10 stages of the original build order are code-complete and unit-tested — 697 tests, all under `tests/unit/` (`tests/integration/` is an empty package). Beyond them: Docker + Caddy deployment, admin dashboard (`/admin`), static test UI (`/`), pending-inclusive balance display, an SSE push channel layered over the original polling, self-service password change + admin password reset, and the reconciliation/corroboration machinery below.
|
||||
All 10 stages of the original build order are code-complete and unit-tested — 698 tests, all under `tests/unit/` (`tests/integration/` is an empty package). Beyond them: Docker + Caddy deployment, admin dashboard (`/admin`), static test UI (`/`), pending-inclusive balance display, an SSE push channel layered over the original polling, self-service password change + admin password reset, and the reconciliation/corroboration machinery below.
|
||||
|
||||
Verified on mainnet with real money: registration + address derivation, deposit crediting (at the time, 1-conf; deposits now need `deposit_confirmations`), a real 10 PLM bet (broadcast → confirmed → change credited back), and one full round cycle (close → draw on a real block hash → 70/30 payout with sat math checked against the broadcast tx → confirmation → close → next round auto-opened). **Withdrawal and the RBF bump path have never been exercised against a live broadcast** — unit-tested only.
|
||||
|
||||
@@ -49,7 +49,7 @@ PYTHONPATH=. python scripts/decrypt_master_key.py # ops recovery: decrypt+pr
|
||||
PYTHONPATH=. python scripts/encrypt_master_key.py # ops bootstrap: import an externally-generated xprv (--overwrite to replace)
|
||||
PYTHONPATH=. python scripts/electrum_smoke_test.py # manual check: connect, handshake, subscribe to headers, print the tip
|
||||
|
||||
python -m pytest # all 697 tests
|
||||
python -m pytest # all 698 tests
|
||||
python -m pytest tests/unit/test_hd.py # one file
|
||||
python -m pytest tests/unit/test_hd.py::test_derivation_is_deterministic # one test
|
||||
```
|
||||
@@ -160,6 +160,7 @@ One connection serves everything — deposit credits, broadcasts, confirmations,
|
||||
- **Rotation.** `ELECTRUM_HOST`/`PORT` is primary, `ELECTRUM_FALLBACK_SERVERS` a comma-separated `host:port[:notls][:pin=<sha256>]` list (`client.py:parse_endpoints` rejects malformed entries at startup, not during the outage when the fallback is needed; `pin=` fixes the server's DER certificate hash, `ELECTRUM_CERT_PIN` does the same for the primary, B-84). The server **refuses to start with fewer than 2 fallbacks** (`config.validate_quorum_servers`, B-84; `ALLOW_NO_QUORUM=true` overrides it, development only). After any failed or dropped session the next server is tried immediately; the backoff (1s doubling to 30s) only kicks in once every server has had a turn.
|
||||
- **Bounded requests** (`_REQUEST_TIMEOUT_SECONDS` = 15s); a timeout tears the connection down. Unbounded waits used to hang `POST /bets` *while holding the per-user lock*, and could stall the confirmation poller permanently.
|
||||
- **The drop is observable**: `client.wait_closed()` resolves when the read loop dies, and `_run_once` races it against the notification consumers and a 60s `server.ping`. A server that keeps answering pings but stopped following the chain is caught too (B-86): at every ping, if no header has been *accepted* for 360s (`_STALL_SECONDS`), `_check_not_stalled` asks the other servers for their tip and ends the session with `ServerStalledError` if any is higher, so `run()` rotates; none higher means a quiet chain and the session goes on. Without it the listener sat on queues nobody would ever fill while `listener.client` still looked alive.
|
||||
- **The chain is checked, not just the headers** (B-99, ELC-30): `ElectrumClient.connect` follows `server.version` with `server.features` and refuses a server whose `genesis_hash` is not PLM's. PLM shares Bitcoin's genesis *and* powLimit, so a Bitcoin Electrum server would pass every header check below; this is what tells them apart, for the quorum's connections too.
|
||||
- **Headers are validated, not trusted** (`_apply_header`, async since B-74): the tip never regresses, a header must meet the difficulty target it claims *and* that target must be no easier than `draw.POW_LIMIT` (PLM's powLimit; `tests/unit/conftest.py` swaps in a regtest limit so tests can mine headers, and the tests of the limit itself put `MAINNET_POW_LIMIT` back), and every advance must be linked to the tip: a single block by its `prev_hash`, a jump by downloading the headers in between (`blockchain.block.headers`, ≤2016 per request) from the active server and checking each. Failure raises `HeaderValidationError`, which ends the session like a dropped connection and rotates away — that header is the draw's only entropy, so a fabricated one picks the winner. The listener keeps the last 10 headers (`_window`, built from the first header plus its 9 predecessors); a header that does not join the tip is a reorg, walked back to its fork point inside the window and followed (`_follow_reorg`), a fork older than the window ends the session. A followed reorg writes `chain_reorg_detected` *before* the window moves (`_record_reorg`, B-85), and a depth of 2 or more pauses the lottery with `paused_reason = 'reorg'` in the same transaction; a header at the tip's own height with a different hash is a reorg too, not ignored. One more header is refused *without* ending the session (B-64): one carrying no `hex` at all (nothing to validate or draw from, and applying the height alone would break the `tip_height`/`tip_header_hex` pairing). `_run_once` separately refuses to publish the client while *no* tip is known, so the ignore-don't-kill choice can't reopen B-63.
|
||||
- **A quorum corroborates every money-moving decision** (`_corroborate_majority`, 10s per server, asking only the *other* endpoints — never the active one, which is what a MITM controls): `corroborate_header` before a block seeds the draw (B-28), `corroborate_utxo_spent` before a UTXO missing from one `listunspent` is written off as externally spent (B-29), and `corroborate_utxo_credit` before a new outpoint credits a balance — same outpoint, same amount, confirmed (B-59). Balances move in both directions, so both directions need the same quorum — and since B-83 that includes `corroborate_utxo_reinstatement` before a UTXO written off as externally spent is put back (same outpoint, amount, confirmed; `deposits/service.py:find_reinstate_candidates` only reads, `reinstate_reappeared_utxos` writes what the quorum agreed on), and `confirmation_proven` before a tx is marked confirmed: `tx/confirmation.py:poll_once` takes the height the active server reports only as a *claim*, and the row confirms when a strict majority of at least 2 of the *other* servers return a `get_merkle` proof that recomputes to the merkle root of the block's header (`electrum/merkle.py`; the header is the tip we hold, or for an older block the one the quorum agrees on and that meets its own target). With no `network` nothing confirms. Since B-82 the pool-coin check before a payout is on the quorum too (`verify_pool_outpoints`, below). A quorum needs **at least 2 answers** and a strict majority of those that answered (B-84): fewer → returns **False**, since one voice decides nothing independently and an unreachable network proves nothing. Only `ALLOW_NO_QUORUM=true` keeps the old reduced rule (no other server → True, otherwise a majority of whoever answers). A failed credit corroboration only *delays*: `find_new_credit_candidates` re-offers the outpoint on the next refresh or `DepositReconciler` sweep.
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ python -m pytest # all tests
|
||||
python -m pytest tests/unit/test_hd.py # one file
|
||||
```
|
||||
|
||||
697 unit tests cover HD derivation, PSBT building, the Electrum client, bets,
|
||||
698 unit tests cover HD derivation, PSBT building, the Electrum client, bets,
|
||||
deposits, withdrawals, the round/draw engine, RBF fee-bumping, admin config,
|
||||
the pending-inclusive balance calculation, and the SSE push channel. No
|
||||
automated integration tests against a live Electrum connection — mainnet
|
||||
|
||||
@@ -787,7 +787,7 @@ flowchart LR
|
||||
*Codice:* `app/static/admin.js:startChainStatusPolling` ·
|
||||
`app/static/admin.js:connectAdminEvents` ·
|
||||
`app/static/admin.js:onAdminServerEvent`
|
||||
*Stato:* da implementare (B-86, B-92), solo per lo stato Electrum e i
|
||||
*Stato:* da implementare (B-92), solo per lo stato Electrum e i
|
||||
filtri dell'audit · *Oggi:* il pannello mostra solo l'altezza della catena,
|
||||
e l'audit log le ultime righe senza filtri.
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ Un limite che il [piano delle correzioni](piano-correzioni.md) chiuderà porta
|
||||
atteso; quando la correzione entra nel codice, il limite si barra come
|
||||
`~~[LIM-nn]~~ Rimosso: chiuso da B-nn`.
|
||||
|
||||
Formato di ogni voce: descrizione, *Impatto*, *Mitigazione attuale*, *Per
|
||||
chiuderlo*, e il codice coinvolto.
|
||||
Formato di ogni voce: descrizione, *Impatto*, *Mitigazione attuale*, *Per
|
||||
chiuderlo*, e il codice coinvolto.
|
||||
|
||||
|
||||
+5
-3
@@ -204,7 +204,7 @@ Note:
|
||||
insieme» di [scommesse-e-prelievi.md](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-98)
|
||||
## 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
|
||||
@@ -247,14 +247,16 @@ cifrava di nuovo il file sbagliato (vedi [operativita.md](operativita.md)).
|
||||
| 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 | **aperto** | — | 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-70, 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-95.
|
||||
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-95, B-99, B-100.
|
||||
- **Aperti:** B-71, B-72 (gravità bassa, operativi o di documentazione) e
|
||||
B-73 … B-98 dalla revisione del 2026-09-28 tranne i corretti (0 critici,
|
||||
3 alti, 5 medi), da implementare nell'ordine di
|
||||
[piano-correzioni.md](piano-correzioni.md).
|
||||
- **Prossimo numero libero:** `B-99`.
|
||||
- **Prossimo numero libero:** `B-101`.
|
||||
Reference in new issue
Block a user