Distinguish a pending-only balance from a truly insufficient one (B-37)
request_withdrawal validated against confirmed UTXOs only and answered a flat insufficient_balance even when the requested amount was covered by the pending-inclusive balance the UI actually shows (unconfirmed change from a recent bet/withdrawal) — contradicting what the user was looking at on screen. Raise balance_pending_confirmation instead when compute_pending_balance covers the amount, carrying the pending sats in params, with its error.* string in all 7 languages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,42 +1,23 @@
|
||||
# Known bugs
|
||||
|
||||
A second full-codebase audit on 2026-07-27 found **25 further issues** (4 critical, 6 high,
|
||||
7 medium, 8 low), listed below as B-33 … B-49. B-25 through B-36 are fixed (see "Previously
|
||||
fixed" below) — no Critical-severity finding remains open; the other 13 are High/Medium/Low.
|
||||
7 medium, 8 low), listed below as B-33 … B-49. B-25 through B-37 are fixed (see "Previously
|
||||
fixed" below) — no Critical-severity finding remains open; the other 12 are High/Medium/Low.
|
||||
The 139-test suite was green at the time of the audit, so none of these were caught by existing
|
||||
coverage — every fix lands with a regression test (the twelve fixes so far brought the suite
|
||||
from 139 to 200).
|
||||
coverage — every fix lands with a regression test (the thirteen fixes so far brought the suite
|
||||
from 139 to 201).
|
||||
|
||||
The recurring pattern across the open findings is worth stating once: the code is rigorous
|
||||
about the failure modes that have actually been hit, and silent about the ones that have not.
|
||||
The payout phase is now fully recoverable; the "drawing" phase (waiting on a block) is now
|
||||
observable (B-36) but still has no equivalent resume-after-restart — see "Known gaps / TODO"
|
||||
in [CLAUDE.md](CLAUDE.md).
|
||||
|
||||
For limitations that are accepted by design rather than bugs (single-shared-token admin auth,
|
||||
single-process assumptions, no user-facing history, etc.), see "Known gaps / TODO" in
|
||||
[CLAUDE.md](CLAUDE.md).
|
||||
in [CLAUDE.md](CLAUDE.md), which is also where other by-design limitations (single-shared-token
|
||||
admin auth, single-process assumptions, no user-facing history, etc.) are documented.
|
||||
|
||||
---
|
||||
|
||||
## Medium
|
||||
|
||||
### B-37 — Displayed balance and spendable balance diverge, and the error does not explain it
|
||||
|
||||
After a bet the change is unconfirmed, so `cached_balance_sats` ≈ 0 while the UI shows
|
||||
`pending_balance_sats` (the real figure). A withdrawal attempted right after validates against
|
||||
**confirmed** UTXOs (`withdrawals/service.py:54-60`) and answers `insufficient_balance`.
|
||||
|
||||
The user sees "1.000 PLM" on screen and is told they have no funds. The mechanism is a
|
||||
documented design decision, but the error does not distinguish "you don't have the money" from
|
||||
"your money is waiting to confirm" — two very different situations for whoever reads it.
|
||||
|
||||
**Proposed fix.** A distinct error code (e.g. `balance_pending_confirmation`) raised when the
|
||||
requested amount is covered by `pending_balance_sats` but not by the confirmed balance,
|
||||
carrying the pending amount in `params`, plus its `error.*` entry in all 7 languages. The
|
||||
withdrawal form should also cap/hint the max against the confirmed balance rather than the
|
||||
displayed one.
|
||||
|
||||
### B-38 — The 500-subscriber SSE cap is a zero-cost DoS of the realtime feature
|
||||
|
||||
`GET /rounds/stream` requires no authentication and each connection takes a slot on a
|
||||
@@ -174,9 +155,10 @@ already does.
|
||||
- **B-34** — password change/reset didn't invalidate already-issued JWTs, so a stolen token survived a change meant to lock it out
|
||||
- **B-35** — API timestamps round-tripped as naive datetimes, so the frontend parsed them as local time instead of UTC
|
||||
- **B-36** — a stalled draw wait had no timeout, no log, and no audit trail, so a frozen round showed nothing in `/admin`
|
||||
- **B-37** — a withdrawal covered by unconfirmed change answered "insufficient balance" instead of distinguishing it from actually having no funds
|
||||
|
||||
See git history for the fix-by-fix breakdown (commits `f13f685`, `50a43ae`, `933760e`, and the
|
||||
B-28/B-29/B-30/B-31/B-32/B-33/B-34/B-35/B-36 fixes). Suite grew from 139 to 200 tests over the twelve.
|
||||
B-28/B-29/B-30/B-31/B-32/B-33/B-34/B-35/B-36/B-37 fixes). Suite grew from 139 to 201 tests over the thirteen.
|
||||
|
||||
A full-codebase audit on 2026-07-26 (commit `d4e0974`) found 24 bugs across every Python
|
||||
module under `app/`, both static frontends, and the Docker/Caddy deployment — 5 critical,
|
||||
|
||||
Reference in New Issue
Block a user