Skip to content

Commit 847d664

Browse files
committed
docs(review): consolidated final review report (1 BLOCKER, 4 should-fix, 4 MEDIUM sec)
1 parent f060117 commit 847d664

5 files changed

Lines changed: 1041 additions & 0 deletions

File tree

docs/handoff/final-review.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Final review — three-PR stack (PRs #6, #7, #8)
2+
3+
**Date:** 2026-05-02
4+
**Branch under review:** `feat/web-and-docs` (tip: `021ffa1`)
5+
**Stack:** PR #6#7#8, all draft, stacked off `main`.
6+
**Tests:** 242 passing, ~17s wall, no flakes.
7+
**Reviewers:** code, security, docs/UX, test coverage (4 parallel agents).
8+
9+
Detailed reports:
10+
- [`review-code.md`](./review-code.md)
11+
- [`review-security.md`](./review-security.md)
12+
- [`review-docs.md`](./review-docs.md)
13+
- [`review-tests.md`](./review-tests.md)
14+
15+
---
16+
17+
## Verdict
18+
19+
**Mergeable after one BLOCKER fix and four small SHOULD-FIX items.**
20+
Stack is well-structured, stdlib-only, hot path untouched, observers cleanly
21+
decoupled, bilingual docs are real (not machine-translated), zero HIGH security
22+
findings, 242 tests passing.
23+
24+
The one blocking bug is in `solo.py` prev-hash byte-order — masked by the
25+
all-zero `FAKE_TEMPLATE` prevhash in `test_solo.py`, so against a real
26+
`bitcoind` solo mode would silently mine invalid blocks.
27+
28+
## Blocker (fix before merge)
29+
30+
### B1 — `solo.py:514-521` prev-hash word-swap is a no-op
31+
32+
`_template_to_job` builds `prev_stratum_hex` such that `swap_words` in
33+
`_build_header_base` cancels out, leaving the mined header with `prev_be`
34+
display order instead of internal little-endian `prev_be[::-1]`. Submit-time
35+
serializer in `_assemble_header` independently does the right reversal, so
36+
hashing-time and submit-time headers disagree.
37+
38+
**Hidden by:** `FAKE_TEMPLATE.previousblockhash = "0" * 64` (fixed point under
39+
any byte permutation). No real-bitcoind integration test catches it.
40+
41+
**Fix sketch** (from code-reviewer):
42+
```python
43+
internal = bytes.fromhex(tmpl["previousblockhash"])[::-1]
44+
prev_stratum_hex = b"".join(internal[i:i+4][::-1]
45+
for i in range(0, 32, 4)).hex()
46+
```
47+
48+
**Test gap to close together:** add a `test_solo.py` case with a real
49+
non-symmetric mainnet block hash and assert `_build_header_base` produces
50+
exactly `prev_display[::-1]`.
51+
52+
## Should-fix (recommend before merge, all small)
53+
54+
### S1 — `docker-compose.yml:10` comment misnames web-dashboard port
55+
Comment points at `:8000` (which is `/metrics`+`/healthz`); web dashboard is
56+
on `:8001`. First-run users hit the Prometheus exposition page. (docs review)
57+
58+
### S2 — `docker run` example in `deploy.{en,ru}.md` §6 has confusing positional
59+
The literal `docker` is the worker_name positional, but reads as a subcommand.
60+
Rename to `mybox` and add a comment. (docs review)
61+
62+
### S3 — `CHANGELOG.md` link footer stale
63+
v0.3.0–v0.7.0 are unlinked; `[Unreleased]` still compares against v0.2.0.
64+
Markdown silently broken. (docs review)
65+
66+
### S4 — README advanced-flags table omits `--log-file`
67+
Flag is real (`cli.py:108`), used in TUI workflow. Missing in EN and RU
68+
halves. (docs review)
69+
70+
## Security MEDIUMs (not blocking the documented use-case, fix before any non-localhost deploy)
71+
72+
### M-1 — `/api/events` SSE has no concurrent-connection cap
73+
Per-connection `queue.Queue(maxsize=256)` + thread; `ThreadingHTTPServer`
74+
spawns one thread per request; loopback default mitigates, but
75+
`docker-compose.yml` flips bind to `0.0.0.0`. **Fix:** `max_subscribers` cap
76+
+ HTTP 503 on overflow.
77+
78+
### M-2 — `BitcoinRPC` cookie file read is unbounded and unguarded
79+
`Path(cookie_path).read_text()` with no size cap and no `try/except`.
80+
Footgun: `--rpc-cookie /tmp` OOMs the miner. **Fix:** stat → size limit 4 KiB
81+
`OSError` → friendly CLI error.
82+
83+
### M-3 — ctypes loads `libcrypto-3.dll` by bare name on Windows
84+
DLL search order includes app dir → writable cwd hijack possible. **Fix:**
85+
`ctypes.WinDLL(..., winmode=LOAD_LIBRARY_SEARCH_SYSTEM32)` or
86+
`os.add_dll_directory()` with a known-good path.
87+
88+
### M-4 — Docker compose binds to `0.0.0.0` + ships Grafana `admin/admin`
89+
`ports: "8001:8001"` and `"3000:3000"` publish broadly; Docker bypasses
90+
host firewall via `iptables`. **Fix:** prefix with `127.0.0.1:`; make
91+
`GRAFANA_PASSWORD` mandatory via `${GRAFANA_PASSWORD:?...}` idiom.
92+
93+
## Test gaps (no blockers, but high-value to add next iteration)
94+
95+
1. **Mid-state ↔ ctypes parity sentinel** — guards against silent endianness
96+
regression in `_worker_ctypes` vs `_worker_hashlib_midstate`. Hardware-
97+
independent, cheap.
98+
2. **`SoloClient.reader_loop` RPC failure path** — uncovered.
99+
3. **`SoloClient` without `default_witness_commitment`** (regtest/non-segwit
100+
templates).
101+
4. **`cli._build_pool_list` and `_resolve_sha_backend`** — pure helpers, zero
102+
tests today. Add `tests/test_cli_helpers.py`.
103+
5. **`webui._serve_events` cleanup on subscriber drop** — verify
104+
`_subscribers` list returns to baseline length after disconnect.
105+
6. **`StatsProvider.publish_event` under concurrent subscribe/unsubscribe**
106+
intentional design (snapshot-then-call) deserves a smoke test.
107+
108+
## Non-blocking code concerns
109+
110+
- `cli.py:460-466``stats_provider.update_hashrate` monkey-patch. Lift
111+
`last_hashrate_ts` into `StatsSnapshot` and drop the wrapper. Already flagged
112+
in PR-A handoff as an open question.
113+
- `notifier.py:307` — dead boolean clause; intent is probably
114+
`if item is None or self._stop_event.is_set():`.
115+
- `solo.py:489-499``deadbeef` extranonce marker has 2⁻³² collision risk.
116+
Use 16-byte `os.urandom`.
117+
- `solo.py:407-452``submit()` runs synchronously in mine thread; OK for
118+
learning code, document in `architecture.{en,ru}.md`.
119+
- `webui.py:331-336``repr(exc)` leaks into healthz HTTP body; loopback
120+
default mitigates.
121+
- `parallel.py:275-276` — pre-existing bare `except Exception: pass` on queue
122+
cleanup. Out of stack scope.
123+
124+
## Docs nice-to-haves
125+
126+
- N1: Validate `--solo` argument tuple before BTC address validation.
127+
- N2: argparse help text is Russian-only; bilingual everywhere else.
128+
- N3: `architecture.{en,ru}.md` doesn't note that telegram inbound requires
129+
`HOPE_HASH_TELEGRAM_INBOUND=1` opt-in.
130+
- N6/N7: `deploy.{en,ru}.md` §3 incorrectly says "503 otherwise" — actual
131+
is 200 for `degraded`, 503 only for `down`.
132+
- N10: `Dockerfile` `EXPOSE` lists 8000+9090 but compose uses 8000+8001.
133+
- N11: `architecture.{en,ru}.md` file-map omits `_logging.py`/`__main__.py`.
134+
135+
## Praise (worth keeping in mind for future agents)
136+
137+
- **`StatsProvider` as canonical bus** — clean decoupling of mine() from TUI,
138+
web, healthz, Prometheus. Pub/sub added in PR C without touching call sites.
139+
- **`PoolList`**`mark_failed` returns rotation flag, `full_cycle_failed`
140+
distinguishes flap from outage, `RLock` prevents the obvious deadlock and
141+
there's a test pinning the invariant.
142+
- **ctypes loader hygiene**`c_void_p` restypes (catches 64-bit truncation),
143+
`EVP_sha256` symbol probe (catches wrong DLL), `try/finally` around
144+
`EVP_MD_CTX_free` (no leak/UAF).
145+
- **`render_html` is fully static** — no user input echoed in, no CDN, no
146+
external scripts. XSS-safe by construction.
147+
- **`test_notifier_timing.py`** — exemplary regression sentinel for the
148+
submit-vs-ack semantic that already bit the project once.
149+
- **`test_solo.py` (38 tests)** — well-spent on the most error-prone module;
150+
byte-level assertions on `_varint`, `_push_data`, `_serialize_height`,
151+
witness commitment parse/compute.
152+
- **Bilingual docs** — EN/RU mirror line-for-line; Russian reads as
153+
written-by-a-human, technical terms stay English by convention.
154+
- **Defaults are conservative**`--web-host 127.0.0.1`, `--web-port 0`,
155+
`HOPE_HASH_TELEGRAM_INBOUND=0` — exactly the right posture.
156+
157+
## Recommended merge order
158+
159+
1. Land **B1** fix + the matching prev-hash test against PR #7 branch.
160+
2. (Optional now / can defer) land **S1–S4** doc/comment fixes against PR #8
161+
branch.
162+
3. (Defer to a follow-up PR) **M-1–M-4** security MEDIUMs and the six
163+
test-coverage additions.
164+
4. Merge PR #6 → main, then rebase #7 onto main + merge, then rebase #8 onto
165+
main + merge.
166+
167+
Total fix budget for blocking + should-fix: ~30 minutes of engineering work
168+
plus tests. The four MEDIUMs and the test gaps are a clean follow-up PR.

0 commit comments

Comments
 (0)