Snapshot a round's timing when it opens (B-61)
round_duration_seconds was read live on every scheduler tick and every bet check, with the deadline computed as opened_at + duration. Lowering it from 600 to 60 while a round was 300s in closed that round instantly; raising it moved the closes_at clients were already counting down to. round_cooldown_seconds had the same property for the gap after a close. B-11 fixed this class of problem for the advertised jackpot; the timing fields were left live. Round now carries duration_seconds and cooldown_seconds, set from the config when it opens. round_deadline() is the single place the deadline is computed — the scheduler, place_bet's two checks and /rounds/current's closes_at all go through it — and the cooldown is read off the round that just closed, so the gap a round announced is the gap that's honoured. The config row becomes what the *next* round opens with. The migration backfills from the live config rather than leaving the column defaults: an instance running 300s rounds would otherwise see the round currently in progress jump to 600s the moment this lands, which is precisely the retroactive change being fixed. Verified against a scratch DB with a non-default config. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ from app.electrum.scripthash import address_to_scripthash
|
||||
from app.rounds.config import get_round_config
|
||||
from app.rounds.draw import draw_winner, header_hex_to_block_hash
|
||||
from app.rounds.events import broadcaster
|
||||
from app.rounds.service import open_new_round_if_needed, winner_share
|
||||
from app.rounds.service import open_new_round_if_needed, round_deadline, winner_share
|
||||
from app.wallet.hd import derive_pool_key
|
||||
from app.wallet.plm_network import PLM_MAINNET
|
||||
from app.wallet.psbt_builder import InsufficientFundsError, Utxo, build_payout_transaction
|
||||
@@ -67,8 +67,11 @@ class RoundScheduler:
|
||||
await session.commit()
|
||||
if round_ is None:
|
||||
return # still in the cooldown window after the last round closed
|
||||
round_id, status, opened_at = round_.id, round_.status, round_.opened_at
|
||||
round_duration_seconds = (await get_round_config(session)).round_duration_seconds
|
||||
round_id, status = round_.id, round_.status
|
||||
# B-61: this round's own snapshotted deadline, not one recomputed from
|
||||
# whatever the config says now — an operator lowering the duration
|
||||
# mid-round used to close the round on the spot.
|
||||
deadline = round_deadline(round_)
|
||||
|
||||
if status == "paying_out":
|
||||
# B-26: _trigger_payout used to run exactly once, from _close_and_draw —
|
||||
@@ -83,8 +86,7 @@ class RoundScheduler:
|
||||
return # "drawing" — progress happens inside the in-flight _close_and_draw call
|
||||
|
||||
if status == "open":
|
||||
opened_at = opened_at.replace(tzinfo=timezone.utc)
|
||||
if datetime.now(timezone.utc) < opened_at + timedelta(seconds=round_duration_seconds):
|
||||
if datetime.now(timezone.utc) < deadline:
|
||||
return
|
||||
|
||||
async with self._session_factory() as session:
|
||||
|
||||
Reference in New Issue
Block a user