Files
plm-lottery/app/electrum
davideandClaude Opus 5 c0314e2bf0 Never swap the tip's hash sideways, and never split it from its height (B-64)
_apply_header's linkage check only fires on a single-block advance, so a header
at the height we already held one for was applied on nothing but its own
self-consistency — and that check, as header_meets_its_own_target's own docstring
says, a server can satisfy with a self-declared easy target. So the one value the
draw is seeded from could be replaced under us at the current height, by a reorg
at the tip or by a single server disagreeing with the rest, with no check able to
speak to it. Separately, a header carrying no hex set tip_header_hex back to None
while advancing tip_height, leaving the two describing different blocks — the
exact pairing that function exists to keep.

Both are now refused without ending the session, unlike the fabrication cases
above them: neither is evidence of a hostile server, and rotating away would cost
us the one connection that also credits deposits and broadcasts transactions.

- A same-height header is ignored (logged when it actually differs). The hash
  committed to for a height is not swapped under us; if ours turns out to be the
  orphan, corroborate_header already refuses to seed a draw from it and the draw
  waits for a further block.
- A hex-less header is ignored outright: nothing to validate, nothing to draw
  from. A server that only ever pushed heights now freezes the draw — visibly,
  via B-36's draw_stalled — instead of costing us the connection.

Because ignoring is not fatal, _run_once additionally refuses to publish the
client when the initial header leaves the tip still unknown, so this cannot
reopen B-63's window from the other side.

The two _run_once tests are bounded with asyncio.wait_for: without their guard
that call waits on session tasks nothing ends, and a regression must fail rather
than hang the suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 10:29:37 +02:00
..