Skip to content

Commit ac867fd

Browse files
committed
fix: reconcile LuckyPot entry requests
1 parent 1633fb5 commit ac867fd

5 files changed

Lines changed: 59 additions & 12 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ wheels/
99

1010
.env
1111
*.db
12+
*.db-shm
13+
*.db-wal
1214
*.log
15+
data/
1316

1417
tmp
1518
PLAN.md

luckypot/db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def get_entry_by_id(conn, entry_id: int) -> dict | None:
148148
def get_entry_by_request_id(conn, request_id: str) -> dict | None:
149149
"""Get a pot entry by its StackCoin request ID, including pot guild_id."""
150150
cursor = conn.execute(
151-
"""SELECT pe.*, p.guild_id AS pot_guild_id
151+
"""SELECT pe.*, p.guild_id AS pot_guild_id, p.is_active AS pot_is_active
152152
FROM pot_entries pe
153153
JOIN pots p ON pe.pot_id = p.pot_id
154154
WHERE pe.stackcoin_request_id = ?""",

luckypot/discord/commands.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ async def invoke(self, ctx: lightbulb.Context) -> None:
2828
discord_id = str(ctx.user.id)
2929

3030
try:
31+
await ctx.defer(ephemeral=True)
3132
guild_announce = partial(announce, guild_id)
3233
result = await enter_pot(
3334
discord_id, guild_id, announce_fn=guild_announce

luckypot/game.py

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ async def enter_pot(
129129
}
130130

131131
stk_user_id = stk_user["id"]
132-
idempotency_key = f"pot_entry:{pot_id}:{discord_id}"
132+
prior_attempts = conn.execute(
133+
"SELECT COUNT(*) AS count FROM pot_entries WHERE pot_id = ? AND discord_id = ?",
134+
(pot_id, discord_id),
135+
).fetchone()["count"]
136+
idempotency_key = f"pot_entry:{pot_id}:{discord_id}:{prior_attempts + 1}"
133137
req = await stk.create_request(
134138
to_user_id=stk_user_id,
135139
amount=POT_ENTRY_COST,
@@ -144,13 +148,23 @@ async def enter_pot(
144148

145149
request_id = str(req["request_id"])
146150

147-
entry_id = db.add_entry(
148-
conn,
149-
pot_id=pot_id,
150-
discord_id=discord_id,
151-
amount=POT_ENTRY_COST,
152-
stackcoin_request_id=request_id,
153-
)
151+
try:
152+
entry_id = db.add_entry(
153+
conn,
154+
pot_id=pot_id,
155+
discord_id=discord_id,
156+
amount=POT_ENTRY_COST,
157+
stackcoin_request_id=request_id,
158+
)
159+
except Exception:
160+
await stk.deny_request(int(request_id))
161+
logger.exception(
162+
f"Failed to persist entry for request_id={request_id}; denied remote request"
163+
)
164+
return {
165+
"status": "error",
166+
"message": "Failed to record your pot entry. The StackCoin request was cancelled; please try again.",
167+
}
154168

155169
return {
156170
"status": "pending",
@@ -171,11 +185,11 @@ def select_random_winner(participants: list[dict]) -> dict | None:
171185
if not participants:
172186
return None
173187

174-
total_weight = sum(p["amount"] for p in participants)
188+
total_weight = sum(max(p["amount"], POT_ENTRY_COST) for p in participants)
175189
roll = random.uniform(0, total_weight)
176190
cumulative = 0
177191
for p in participants:
178-
cumulative += p["amount"]
192+
cumulative += max(p["amount"], POT_ENTRY_COST)
179193
if roll <= cumulative:
180194
return p
181195
return participants[-1]
@@ -461,7 +475,26 @@ async def on_request_accepted(
461475
discord_id = entry["discord_id"]
462476
announce_fn = partial(announce, guild_id) if announce else None
463477

464-
if entry["status"] == "pending":
478+
if entry["status"] == "pending" and not entry["pot_is_active"]:
479+
refunded = await send_winnings_to_user(
480+
discord_id,
481+
entry["amount"],
482+
idempotency_key=f"pot_refund:{request_id}",
483+
)
484+
if refunded:
485+
db.deny_entry(conn, entry_id)
486+
logger.info(
487+
f"Entry {entry_id} refunded for discord_id={discord_id}; pot already ended"
488+
)
489+
if announce_fn:
490+
await announce_fn(
491+
f"<@{discord_id}>'s late pot payment was refunded because that pot already ended."
492+
)
493+
else:
494+
logger.error(
495+
f"Failed to refund late accepted entry {entry_id} for discord_id={discord_id}"
496+
)
497+
elif entry["status"] == "pending":
465498
db.confirm_entry(conn, entry_id)
466499
logger.info(f"Entry {entry_id} confirmed for discord_id={discord_id}")
467500
if announce_fn:

luckypot/stk.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ async def create_request(
141141
return None
142142

143143

144+
async def deny_request(request_id: int) -> bool:
145+
"""Deny a payment request. Returns True if the request was denied."""
146+
try:
147+
result = await get_client().deny_request(request_id=request_id)
148+
return result.success is True
149+
except stackcoin.StackCoinError as e:
150+
logger.error(f"Failed to deny request {request_id}: {e}")
151+
return False
152+
153+
144154
async def get_request(request_id: int) -> dict | None:
145155
"""Get a request by ID."""
146156
try:

0 commit comments

Comments
 (0)