davideandClaude Sonnet 5.5 a3c51719bd Confirm transactions by merkle proof and reinstate coins only on a quorum (B-83, TX-06, TX-22, TX-23, ELC-25, ELC-26, DEP-02, DEP-08, AUD-18, ADR-07)
poll_once marked a transaction confirmed on one server's `height > 0`, and
reinstate_reappeared_utxos restored a coin written off as spent outside the platform on
the active server's word alone, so a hostile active server could report a
never-relayed payout as confirmed (winner unpaid, round closed) or put a spent coin
back into a balance.

- electrum/merkle.py: pure merkle-branch verification against a block header.
- ElectrumListener.confirmation_proven(txid, height): the *other* servers are asked for
  blockchain.transaction.get_merkle; a strict majority of at least the quorum minimum
  must return a proof that recomputes to the merkle root of that block's header (block
  height and position checked too). The header is the tip the listener holds, or for an
  older block the one a majority of the quorum agrees on and that meets its own target;
  a height above our tip cannot be proven. Quorum rules are _corroborate_majority's.
- poll_once takes the listener as `network`: the active server's history only nominates
  a candidate (txid, height); the row confirms only when the proof holds, and stays
  pending otherwise. With no network nothing confirms. ConfirmationPoller wires it.
- The reconciler's late-confirmation check of failed rows (B-78) now puts each claimed
  (txid, height) to the same proof instead of counting histories.
- refresh_user splits reinstatement like a credit: find_reinstate_candidates (read-only;
  same outpoint, recorded amount, confirmed) -> corroborate_utxo_reinstatement (same
  quorum as a credit) -> reinstate_reappeared_utxos writes only what was corroborated,
  rechecking the mark under the lock. Nothing is committed in the read phase any more.
- Broadcast fan-out, the third part of the finding, shipped with B-76.

Tests changed because the spec changed: every poll_once call now passes a network that
proves (FakeNetwork) — the confirmation tests are otherwise unchanged; the failed-row
"quorum does not confirm" test now asserts the claim goes to the proof.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 15:29:30 +02:00
2026-07-21 11:14:56 +02:00

PLM Lottery

A periodic-round lottery system built on PLM, a Bitcoin-like coin (mainnet). Each user gets a dedicated server-derived P2WPKH address; they deposit PLM to that address, place a fixed-cost bet to enter the current round, and when the round closes a winner is drawn who receives 70% of the prize pool (the remaining 30% goes to fees).

This is a custodial system: private keys are generated and held server-side, encrypted at rest. See CLAUDE.md for the full architecture, domain decisions, and known gaps before treating this as production-ready.

Quick start

The server always runs via Docker (app + Caddy reverse proxy with automatic TLS) — in dev and production alike, with only SITE_ADDRESS differing between the two. There's no supported way to run uvicorn directly; the venv is only for local tooling (tests, Alembic migrations, the one-time key scripts) — see CLAUDE.md.

cp .env.example .env               # then fill in the generated secrets, see docs/setup.md
mkdir -p data/db data/keys data/logs
docker compose run --rm app python scripts/generate_master_key.py
docker compose up -d --build

Open https://localhost/ for the test UI, https://localhost/admin for the admin dashboard (a self-signed-certificate warning on first visit is expected in dev — accept it, or use curl -k). The interactive API docs at /docs are disabled by default (they'd otherwise expose the whole API surface, admin endpoints included) — set ENABLE_API_DOCS=true in .env to enable them.

See docs/setup.md and docs/running-the-server.md for the full walkthrough (secrets, master key generation, production TLS with a real domain).

Documentation

  • CLAUDE.md — architecture, commands, domain decisions, known gaps (for anyone/anything working on the code)
  • docs/README.md — index of all documentation (docs-first, single source of truth, one ADR per decision: docs/adr/)
  • docs/come-funziona.md — the specification (Italian): how the platform works, with diagrams and traceable requirement IDs; it indexes every per-topic document (flows, architecture, data model, API, security, audit log, operations, decisions and limits, bug history). docs/processo-di-modifica.md describes how a change goes from the spec to the code
  • docs/setup.md — one-time setup (secrets, master key, migrations)
  • docs/running-the-server.md — how to launch it with Docker+Caddy (dev vs. production TLS)
  • docs/guida-utente.md — end-user guide to the test UI (Italian)
  • docs/guida-admin.md — admin dashboard guide (Italian)

Tech stack

Python (FastAPI, SQLAlchemy async + Alembic, Argon2 + JWT auth), Electrum protocol for PLM network access (no full node), Docker + Caddy for deployment. See CLAUDE.md for the complete list and the reasoning behind each choice.

Testing

python -m pytest                    # all tests
python -m pytest tests/unit/test_hd.py   # one file

556 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 verification so far has been manual (see CLAUDE.md's "Project status").

S
Description
No description provided
Readme
2.2 MiB
0 Stars 1 Watchers 0 Forks
Languages
Python 84.7%
JavaScript 10.3%
HTML 2.5%
CSS 2.3%