Give every payout a bump reserve output and refuse configs that cannot pay a round (B-79, RND-21, RND-23, RND-24, RND-27, RND-38, RND-40, DAT-73, API-31, API-36, TX-11, ADR-17, ADR-29)
The pool normally holds exactly the round's bet outputs and the payout spent them all for exactly the pool amount, so it had no change output: bump_fee could never accelerate it, and a payout stuck in a mempool (never abandoned while a server knows it) kept the round in `paying_out` and the whole lottery stopped. - build_payout_transaction always has three outputs: winner, fee address, and a bump reserve of max(DUST_LIMIT_SATS, estimate_vsize(inputs, 3) * 10 sat/vB) paid to the reserve address m/84'/746'/0'/1/0. The reserve comes out of the platform's 30%, the network fee still out of the winner's 70%. Input value beyond winner share + commission (only rounds opened before the per-round pool address) is folded into the reserve output if >= dust, left to the network fee otherwise. `change_address` is replaced by `reserve_address`. - `rounds.bump_reserve_sats` (Alembic e5a91c3d7b26, plain add_column) is written with the payout intent before the broadcast; /admin/rounds returns it. - bump_fee takes the increase from the reserve output for a payout (_find_reducible_output; a payout already in flight with a change to the pool still falls back to that), never from the winner or fee-address outputs. - payout_config_problem / rounds_can_open (RND-40): a bet amount and fee rate under which a round of 1 or of MAX_PARTICIPANTS_PER_ROUND participants could not cover the network fee, the reserve and dust outputs opens no round (lottery_configured=false); PUT /admin/config refuses such a bet amount or fee rate with 422 config_not_payable, judged on the resulting values, since the payout reads the fee rate live. Ambiguity resolved: the reserve output and any excess input are one output (RND-23 says one output to the reserve address), and bump_reserve_sats records that output's initial value. DAT-22's "abandon clears all amounts" stays with B-96. Tests changed because the spec changed: the four test_payout_builder tests asserted the old two/three-output shape with change to the pool and the fee address receiving the whole 30%; the whitespace fee_address test now spells out the amounts rounds_can_open reads. New: reserve sizing, payout with 1 and with 400 participants (inputs = outputs + fee to the satoshi), exact commission/reserve/dust boundary, excess folded or left to the fee, a real payout bumped out of its reserve with the other outputs byte-identical and bumped five times, the old reserve-less shape still raising RbfError, the scheduler output addresses and recorded amounts, payability boundaries found independently by bisection, no round / unconfigured flag for an unpayable config, a round in progress surviving it, PUT /admin/config refusals and the migration column. Mutation-checked: dropping the reserve lookup in bump_fee and the payability check in rounds_can_open each fail tests. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
473d541ab5
commit
daa85cf4f0
27 files changed
+684
-139
No files matched your search
+13
-15
@@ -115,7 +115,7 @@ erDiagram
|
||||
bigint pool_amount_sats
|
||||
bigint winner_amount_sats
|
||||
bigint fee_amount_sats
|
||||
bigint bump_reserve_sats "da implementare"
|
||||
bigint bump_reserve_sats
|
||||
string payout_txid
|
||||
datetime payout_blocked_at "da implementare"
|
||||
}
|
||||
@@ -446,7 +446,7 @@ restano nella tabella come storico.
|
||||
| `pool_amount_sats` | BigInteger | sì | `NULL` | Montepremi: somma delle scommesse confermate e ancora sulla catena ([DAT-21], [DAT-67]) | `_close_and_draw`, `_complete_draw`; `RoundScheduler._trigger_payout` (atteso, B-82) |
|
||||
| `winner_amount_sats` | BigInteger | sì | `NULL` | Quanto riceve il vincitore (70% meno la commissione di rete) | `RoundScheduler._trigger_payout` |
|
||||
| `fee_amount_sats` | BigInteger | sì | `NULL` | Quanto va a `fee_address`: 30% più il resto dell'arrotondamento, meno `bump_reserve_sats` nello schema atteso | `_trigger_payout` |
|
||||
| `bump_reserve_sats` | BigInteger | sì | `NULL` | Output di riserva per l'RBF del pagamento, verso l'indirizzo di riserva ([DAT-73]) — *da implementare (B-79)* | `_trigger_payout`; `app/tx/broadcast.py:bump_fee` |
|
||||
| `bump_reserve_sats` | BigInteger | sì | `NULL` | Output di riserva per l'RBF del pagamento, verso l'indirizzo di riserva ([DAT-73]) | `_trigger_payout`; `app/tx/broadcast.py:bump_fee` |
|
||||
| `payout_txid` | String(64) | sì | `NULL` | Txid corrente della transazione di pagamento | `_trigger_payout`; `_retarget_txid_references`; `app/tx/reconcile.py:_abandon` |
|
||||
| `payout_blocked_at` | DateTime | sì | `NULL` | Il pagamento è trattenuto perché la scommessa del vincitore non è più sulla catena ([DAT-68]) — *da implementare (B-82)* | `_trigger_payout`; `app/api/routes/admin.py:resume_lottery` |
|
||||
|
||||
@@ -537,10 +537,10 @@ restano nella tabella come storico.
|
||||
`app/tx/broadcast.py:_retarget_txid_references` · `app/tx/reconcile.py:_abandon` ·
|
||||
*Test:* `tests/unit/test_scheduler.py`, `tests/unit/test_broadcast.py`,
|
||||
`tests/unit/test_reconcile.py`
|
||||
*Stato:* da implementare (B-96, B-79) · *Oggi:* l'abbandono azzera solo
|
||||
`payout_txid` e lascia `winner_amount_sats`/`fee_amount_sats` del tentativo
|
||||
abbandonato; non esiste un output di riserva e il bump riduce un resto del
|
||||
pool che normalmente non c'è.
|
||||
*Stato:* da implementare (B-96) · *Oggi:* l'abbandono azzera solo
|
||||
`payout_txid` e lascia `winner_amount_sats`, `fee_amount_sats` e
|
||||
`bump_reserve_sats` del tentativo abbandonato (le riscrive il tentativo
|
||||
successivo).
|
||||
|
||||
- **[DAT-60]** Ogni round ha il proprio indirizzo del montepremi,
|
||||
`pool_address`, derivato all'apertura da `m/84'/746'/0'/2/<id>` e scritto
|
||||
@@ -646,9 +646,8 @@ restano nella tabella come storico.
|
||||
è in [ciclo-di-vita-del-round.md](ciclo-di-vita-del-round.md) (B-79).
|
||||
*Codice:* `app/rounds/scheduler.py:RoundScheduler._trigger_payout` ·
|
||||
`app/wallet/psbt_builder.py:build_payout_transaction` ·
|
||||
`app/tx/broadcast.py:bump_fee` · *Test:* —
|
||||
*Stato:* da implementare (B-79) · *Oggi:* la colonna non esiste; il
|
||||
pagamento non ha di norma alcun resto e quindi non si può accelerare.
|
||||
`app/tx/broadcast.py:bump_fee` ·
|
||||
*Test:* `tests/unit/test_scheduler.py::test_trigger_payout_always_carries_a_bump_reserve_to_the_reserve_address`, `tests/unit/test_broadcast.py::test_a_payout_is_bumped_out_of_its_reserve_output_and_nothing_else`, `tests/unit/test_migrations.py::test_the_bump_reserve_column_exists_at_head_and_is_nullable`
|
||||
|
||||
### `round_participants`
|
||||
|
||||
@@ -1276,11 +1275,10 @@ letti con una scansione completa.
|
||||
`app/rounds/scheduler.py:RoundScheduler._trigger_payout` ·
|
||||
`app/wallet/psbt_builder.py:build_payout_transaction` ·
|
||||
*Test:* `tests/unit/test_payout_builder.py`, `tests/unit/test_scheduler.py`
|
||||
*Stato:* da implementare (B-79, B-82) · *Oggi:* il pagamento sceglie fra
|
||||
tutti gli UTXO confermati dell'indirizzo del round e può avere un resto
|
||||
verso quello stesso indirizzo; non c'è
|
||||
riserva, e vale `pool_amount_sats` = `winner_amount_sats` +
|
||||
`fee_amount_sats` + commissione di rete.
|
||||
*Stato:* da implementare (B-82) · *Oggi:* il pagamento sceglie fra
|
||||
tutti gli UTXO confermati dell'indirizzo del round, senza la verifica di
|
||||
[DAT-67]; l'identità con la riserva vale già per i round pagati con tutti
|
||||
e soli i loro ingressi.
|
||||
|
||||
- ~~**[DAT-47]**~~ Assorbito da [RND-12](ciclo-di-vita-del-round.md): l'attesa
|
||||
delle scommesse in volo prima dell'estrazione è comportamento del round. Il
|
||||
@@ -1467,7 +1465,7 @@ In ordine di applicazione (ogni riga rivede la precedente):
|
||||
Revisioni attese, non ancora scritte (ciascuna nel commit della sua voce,
|
||||
con i backfill di [DAT-55]): `rounds` (`bet_amount_sats`,
|
||||
`fee_address`, `draw_confirmations`, `server_seed`, `server_seed_hash`,
|
||||
`participants_hash`, `draw_height`, `bump_reserve_sats`, `payout_blocked_at`),
|
||||
`participants_hash`, `draw_height`, `payout_blocked_at`),
|
||||
`round_participants` (`bet_vout`, `draw_position` e il vincolo unico),
|
||||
`round_config` (`deposit_confirmations`, `draw_confirmations`,
|
||||
`max_round_pool_sats`, `paused_reason`, con `paused_reason = 'operator'` per
|
||||
|
||||
Reference in new issue
Block a user