Abandon a transaction only on proof, and keep watching failed rows (B-78, TX-15, TX-17, TX-23, TX-26, ELC-28, DAT-33, DAT-36, DAT-69, DAT-72, AUD-11, AUD-12, AUD-31, AUD-39, API-32)
2026-10-14 15:07:09 +02:00
|
|
|
"""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})
|
2026-10-18 00:43:45 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|