Cap participants per round and give the payout its own input limit (B-52)

The payout has to spend one pool UTXO per bet, so reusing MAX_TX_INPUTS (50)
for it made any round past ~50 players unpayable: select_utxos raised
too_many_inputs, the round stayed "paying_out" retrying every 60s forever, and
since no new round may open while one is active, the whole lottery stopped with
the pool stuck. The cap was being enforced on the payout side, i.e. discovered
once the money was already committed and there was no way back.

Two halves:

- select_utxos takes the cap as a parameter. Bets and withdrawals keep
  MAX_TX_INPUTS = 50, which protects a user from a fee that eats into the amount
  they are moving; the payout uses MAX_PAYOUT_TX_INPUTS = 500, where that
  argument doesn't apply — 400 inputs at 1 sat/vB cost ~0.00027 PLM out of the
  winner's 70% share. What actually bounds it is relay policy: 500 inputs is
  ~34 kvB against the 100 kvB standardness limit, and signing that many measures
  ~0.4s, once per round, inside a background task.

- place_bet refuses the 401st bet with a new round_full error (translated into
  all 7 languages), so "a round can always be paid out" is an invariant checked
  before any money moves. MAX_PARTICIPANTS_PER_ROUND sits below the input cap to
  leave the payout headroom for pool change from earlier rounds, and counts every
  participant row rather than only confirmed ones, since a failed bet frees a slot.

A round already wedged past the old cap now pays out on the next retry tick.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-03 16:21:33 +02:00
co-authored by Claude Opus 5
parent 99d7a1ee00
commit 025754c860
10 changed files with 311 additions and 28 deletions
+7
View File
@@ -139,6 +139,7 @@ const TRANSLATIONS = {
'error.network_unavailable': 'Not connected to the network, please try again shortly.',
'error.no_round_open': 'No round is open right now, please try again shortly.',
'error.round_closing': 'The current round is closing, please try again shortly.',
'error.round_full': 'This round has reached its maximum of {max_participants} players — wait for the next one, it opens shortly.',
'error.already_betting': 'You already have an active bet in the current round.',
'error.insufficient_balance': 'Insufficient balance.',
'error.balance_pending_confirmation': 'You have {pending_plm} PLM pending confirmation — it is not spendable yet.',
@@ -299,6 +300,7 @@ const TRANSLATIONS = {
'error.network_unavailable': 'Nessuna connessione alla rete, riprova tra poco.',
'error.no_round_open': 'Nessun round aperto in questo momento, riprova tra poco.',
'error.round_closing': 'Il round corrente si sta chiudendo, riprova tra poco.',
'error.round_full': 'Questo round ha raggiunto il massimo di {max_participants} giocatori — aspetta il prossimo, si apre tra poco.',
'error.already_betting': 'Hai già una bet attiva nel round corrente.',
'error.insufficient_balance': 'Saldo insufficiente.',
'error.balance_pending_confirmation': 'Hai {pending_plm} PLM in attesa di conferma — non ancora disponibili per la spesa.',
@@ -459,6 +461,7 @@ const TRANSLATIONS = {
'error.network_unavailable': 'Sin conexión con la red, inténtalo de nuevo en un momento.',
'error.no_round_open': 'No hay ninguna ronda abierta ahora mismo, inténtalo de nuevo en un momento.',
'error.round_closing': 'La ronda actual se está cerrando, inténtalo de nuevo en un momento.',
'error.round_full': 'Esta ronda ha alcanzado su máximo de {max_participants} jugadores: espera la siguiente, se abre en breve.',
'error.already_betting': 'Ya tienes una apuesta activa en la ronda actual.',
'error.insufficient_balance': 'Saldo insuficiente.',
'error.balance_pending_confirmation': 'Tienes {pending_plm} PLM pendientes de confirmación — todavía no se pueden gastar.',
@@ -619,6 +622,7 @@ const TRANSLATIONS = {
'error.network_unavailable': 'Pas de connexion au réseau, réessayez dans un instant.',
'error.no_round_open': "Aucun round n'est ouvert pour le moment, réessayez dans un instant.",
'error.round_closing': 'Le round en cours est en train de se fermer, réessayez dans un instant.',
'error.round_full': 'Ce tour a atteint son maximum de {max_participants} joueurs — attendez le prochain, il ouvre dans un instant.',
'error.already_betting': 'Vous avez déjà une mise active dans le round en cours.',
'error.insufficient_balance': 'Solde insuffisant.',
'error.balance_pending_confirmation': 'Vous avez {pending_plm} PLM en attente de confirmation — pas encore disponibles.',
@@ -779,6 +783,7 @@ const TRANSLATIONS = {
'error.network_unavailable': 'Keine Verbindung zum Netzwerk, bitte versuche es gleich erneut.',
'error.no_round_open': 'Derzeit ist keine Runde offen, bitte versuche es gleich erneut.',
'error.round_closing': 'Die laufende Runde wird gerade geschlossen, bitte versuche es gleich erneut.',
'error.round_full': 'Diese Runde hat ihr Maximum von {max_participants} Spielern erreicht — warten Sie auf die nächste, sie beginnt in Kürze.',
'error.already_betting': 'Du hast bereits eine aktive Wette in der laufenden Runde.',
'error.insufficient_balance': 'Nicht genügend Guthaben.',
'error.balance_pending_confirmation': 'Sie haben {pending_plm} PLM, die noch auf Bestätigung warten — noch nicht verfügbar.',
@@ -939,6 +944,7 @@ const TRANSLATIONS = {
'error.network_unavailable': 'Нет соединения с сетью, повторите попытку чуть позже.',
'error.no_round_open': 'Сейчас нет открытого раунда, повторите попытку чуть позже.',
'error.round_closing': 'Текущий раунд закрывается, повторите попытку чуть позже.',
'error.round_full': 'В этом раунде достигнут максимум участников ({max_participants}) — дождитесь следующего, он начнётся совсем скоро.',
'error.already_betting': 'У вас уже есть активная ставка в текущем раунде.',
'error.insufficient_balance': 'Недостаточно средств.',
'error.balance_pending_confirmation': 'У вас есть {pending_plm} PLM, ожидающих подтверждения — они пока недоступны для расходования.',
@@ -1099,6 +1105,7 @@ const TRANSLATIONS = {
'error.network_unavailable': '未连接到网络,请稍后重试。',
'error.no_round_open': '当前没有开放的回合,请稍后重试。',
'error.round_closing': '当前回合正在结束,请稍后重试。',
'error.round_full': '本轮已达到 {max_participants} 名玩家的上限 —— 请等待下一轮,很快就会开始。',
'error.already_betting': '你在当前回合已有一笔有效下注。',
'error.insufficient_balance': '余额不足。',
'error.balance_pending_confirmation': '您有 {pending_plm} PLM 待确认 —— 尚不可用于支出。',