START_PAUSED=true pauses the lottery as the operator before the scheduler exists. On the first Electrum session the 20 derivation indexes past the highest assigned one are looked up on every server; if any has history, registration stays closed (503 registration_unavailable) and restore_gap_detected is written, so a restored database cannot hand a lost user's address to a new one. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
46 lines
2.1 KiB
Python
46 lines
2.1 KiB
Python
"""Pausing the lottery from code (B-78, DAT-69, AUD-11).
|
|
|
|
A pause only stops the *next* round from opening (rounds/service.py:rounds_can_open); a
|
|
round in progress still closes, draws and pays. Besides the operator's own switch there
|
|
are pauses the system raises itself when something needs a human look — a transaction
|
|
given up for dead that confirmed after all (`resurrected_tx`, B-78) and, in later
|
|
findings, a deep reorg (`reorg`, B-85) and a payout whose winning bet left the chain
|
|
(`payout_blocked`, B-82).
|
|
"""
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.audit.log import write_audit_log
|
|
from app.rounds.config import get_round_config
|
|
|
|
OPERATOR_REASON = "operator"
|
|
AUTOMATIC_REASONS = ("reorg", "payout_blocked", "resurrected_tx")
|
|
|
|
|
|
async def pause_automatically(session: AsyncSession, reason: str) -> None:
|
|
"""Pause the lottery for an automatic `reason` in the caller's transaction and write
|
|
the `lottery_paused` row. An automatic reason is written even over a pause that is
|
|
already active (DAT-69): the cause that needs checking must not be hidden behind an
|
|
earlier one, and an operator pause is never what overrides it."""
|
|
if reason not in AUTOMATIC_REASONS:
|
|
raise ValueError(f"not an automatic pause reason: {reason!r}")
|
|
config = await get_round_config(session)
|
|
config.paused = True
|
|
config.paused_reason = reason
|
|
await write_audit_log(session, "lottery_paused", {"reason": reason})
|
|
|
|
|
|
async def pause_by_operator(session: AsyncSession, note: str | None = None):
|
|
"""The operator's pause (`POST /admin/pause`, B-90's `START_PAUSED`): never overwrites
|
|
an automatic reason already in force — that is the one that needs a look — and the
|
|
audit row always says `operator`. Returns the config row; the caller commits."""
|
|
config = await get_round_config(session)
|
|
if not (config.paused and config.paused_reason in AUTOMATIC_REASONS):
|
|
config.paused_reason = OPERATOR_REASON
|
|
config.paused = True
|
|
payload = {"reason": OPERATOR_REASON}
|
|
if note:
|
|
payload["note"] = note
|
|
await write_audit_log(session, "lottery_paused", payload)
|
|
return config
|