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:
davideandClaude Sonnet 5.5 committed 2026-10-16 13:55:51 +02:00
1 parent 473d541ab5
commit daa85cf4f0
27 files changed
+684 -139

No files matched your search

@@ -1,6 +1,6 @@
# ADR-17 — La commissione del pagamento esce dalla quota del vincitore; la riserva per l'RBF esce dalla quota della piattaforma
- **Stato:** Accettata — implementazione da completare (B-79)
- **Stato:** Accettata
- **Area:** Economia del gioco
- **Fonte di verità per:** la scelta di progetto descritta qui sotto; i requisiti (`PREFISSO-NN`) che la attuano stanno nei documenti di dominio, indicati nelle *Conseguenze*.
@@ -30,6 +30,4 @@ commissione verso `fee_address` è quindi il 30% meno la riserva.
[ciclo-di-vita-del-round.md](../ciclo-di-vita-del-round.md#4--pagamento) (B-79).
*Codice:* `app/wallet/psbt_builder.py:build_payout_transaction` ·
*Test:* `tests/unit/test_payout_builder.py::test_payout_deducts_fee_only_from_winner_share`
*Stato:* da implementare (B-79) · *Oggi:* nessuna riserva: il 30% va intero a
`fee_address` e, nel caso normale, il pagamento non ha resto.
*Test:* `tests/unit/test_payout_builder.py::test_payout_deducts_fee_only_from_winner_share`, `tests/unit/test_payout_builder.py::test_payout_at_the_participant_cap_pays_the_reserve_out_of_the_platform_share`, `tests/unit/test_broadcast.py::test_a_payout_is_bumped_out_of_its_reserve_output_and_nothing_else`
@@ -1,6 +1,6 @@
# ADR-29 — Senza `fee_address` non si apre nessun round
- **Stato:** Accettata — implementazione da completare (B-79)
- **Stato:** Accettata
- **Area:** Round ed estrazione
- **Fonte di verità per:** la scelta di progetto descritta qui sotto; i requisiti (`PREFISSO-NN`) che la attuano stanno nei documenti di dominio, indicati nelle *Conseguenze*.
@@ -19,6 +19,4 @@ round impagabile va aggiunta nello stesso punto.
l'RBF e una commissione sopra la soglia di polvere ([ADR-17](ADR-17-commissione-pagamento-esce-dalla-quota-vincitore.md), B-79).
*Codice:* `app/rounds/service.py:rounds_can_open` · `app/rounds/config.py:get_round_config` ·
*Test:* `tests/unit/test_rounds_service.py::test_withholds_new_round_while_no_fee_address_is_configured`, `tests/unit/test_rounds_service.py::test_a_round_in_progress_survives_the_fee_address_being_cleared`, `tests/unit/test_rounds_route.py::test_lottery_configured_flags_a_missing_fee_address`
*Stato:* da implementare (B-79) · *Oggi:* `rounds_can_open` controlla solo
`fee_address`.
*Test:* `tests/unit/test_rounds_service.py::test_withholds_new_round_while_no_fee_address_is_configured`, `tests/unit/test_rounds_service.py::test_a_round_in_progress_survives_the_fee_address_being_cleared`, `tests/unit/test_rounds_route.py::test_lottery_configured_flags_a_missing_fee_address`, `tests/unit/test_rounds_service.py::test_no_round_opens_while_the_config_could_not_pay_one`, `tests/unit/test_rounds_route.py::test_lottery_configured_is_false_when_the_amounts_could_not_pay_a_round`
+8 -3
View File
@@ -789,7 +789,12 @@ dei parametri operativi in
- **Risposta `200`:** la configurazione completa, come
[API-30](#get-adminconfig).
- **Errori:** `422` per qualunque vincolo violato (nessun campo viene
applicato).
applicato). Una modifica di `bet_amount_sats` o `fee_rate_sat_vb` con cui
un round non si potrebbe pagare ([RND-40](ciclo-di-vita-del-round.md#4--pagamento))
dà `422` con `detail = {"code": "config_not_payable", "message", "params":
{"reason"}}`, dove `reason` è `winner_share_below_network_fee` o
`commission_below_dust_limit`; il controllo usa i valori risultanti dopo
la modifica.
- **Effetti:** scrive `config_updated` con i valori prima/dopo dei soli
campi davvero cambiati; nessuna scrittura se nulla cambia. Durata, pausa
tra round, costo della scommessa, `fee_address` e `draw_confirmations` si
@@ -888,8 +893,8 @@ dei parametri operativi in
completa di un round è in [API-45](#get-roundsround_idproof).
*Codice:* `app/api/routes/admin.py:list_rounds` ·
*Test:* `tests/unit/test_admin.py`
*Stato:* da implementare (B-79, B-82), per gli ultimi due campi · *Oggi:*
la lista si ferma a `draw_height`.
*Stato:* da implementare (B-82), per `payout_blocked_at` · *Oggi:*
la lista si ferma a `bump_reserve_sats`.
#### GET /admin/audit-log
+1 -1
View File
@@ -194,7 +194,7 @@ sono vuoti e non sono elencati.
| `app/wallet/hd.py` | Generazione della master key, derivazione di indirizzi/chiavi utente (`0/i`), del montepremi di ogni round (`2/<round_id>`, B-80) e dell'indirizzo di riserva (`1/0`), `derive_round_pool_key` che sceglie la chiave che controlla l'indirizzo salvato sul round, export WIF; la chiave d'account decifrata resta in cache nel processo |
| `app/wallet/address.py` | `is_valid_plm_address`: checksum bech32 + HRP `plm` |
| `app/wallet/balance.py` | `recompute_balance` (saldo confermato), `compute_pending_balance` (saldo mostrato) — vedi [registrazione-e-depositi.md](registrazione-e-depositi.md) |
| `app/wallet/psbt_builder.py` | Selezione UTXO, costruzione e firma di scommessa, prelievo e pagamento; limiti `MAX_TX_INPUTS`, `MAX_PAYOUT_TX_INPUTS`, `MAX_PARTICIPANTS_PER_ROUND`, `DUST_LIMIT_SATS`. *Da implementare:* uscita di riserva per l'RBF sul pagamento (B-79) |
| `app/wallet/psbt_builder.py` | Selezione UTXO, costruzione e firma di scommessa, prelievo e pagamento; limiti `MAX_TX_INPUTS`, `MAX_PAYOUT_TX_INPUTS`, `MAX_PARTICIPANTS_PER_ROUND`, `DUST_LIMIT_SATS`; l'uscita di riserva per l'RBF sul pagamento (`payout_bump_reserve`, B-79) |
### Rete Electrum
+15 -28
View File
@@ -94,8 +94,6 @@ richiesta di scommessa (vedi
*Codice:* `app/rounds/service.py:open_new_round_if_needed`, `app/rounds/service.py:rounds_can_open` ·
*Test:* `tests/unit/test_rounds_service.py`
*Stato:* da implementare (B-79) · *Oggi:* `rounds_can_open` controlla solo
che `fee_address` non sia vuoto.
- **[RND-05]** All'apertura il round **fotografa** i parametri correnti che lo
governano: durata (`round_duration_seconds`, 600s predefiniti), pausa
successiva (`round_cooldown_seconds`, 30s predefiniti), costo della
@@ -413,9 +411,10 @@ del montepremi del round.
`/admin`.
*Codice:* `app/rounds/service.py:winner_share`, `app/rounds/scheduler.py:RoundScheduler._trigger_payout` ·
*Test:* `tests/unit/test_payout_builder.py`
*Stato:* da implementare (B-79, B-82) · *Oggi:* la ripartizione si calcola
su `pool_amount_sats` fissato all'estrazione e l'indirizzo delle
commissioni riceve l'intera quota del 30%.
*Stato:* da implementare (B-82) · *Oggi:* la ripartizione si calcola
su `pool_amount_sats` fissato all'estrazione, senza la verifica di
[RND-39]; la riserva per l'RBF è già tolta dalla quota delle commissioni
(B-79).
- **[RND-22]** Il pagamento spende le uscite delle scommesse dei partecipanti
del round **verificate** ([RND-39]), all'indirizzo del montepremi del round
([RND-35]), e nient'altro: altre monete arrivate a quell'indirizzo non
@@ -447,11 +446,10 @@ del montepremi del round.
si costruisce (`winner_share_below_network_fee`,
`commission_below_dust_limit`).
*Codice:* `app/wallet/psbt_builder.py:build_payout_transaction` ·
*Test:* `tests/unit/test_payout_builder.py` (nessun test copre `commission_below_dust_limit`)
*Stato:* da implementare (B-79, B-95) · *Oggi:* la terza uscita è un resto
verso l'indirizzo del montepremi, presente solo se è di almeno 294 sat e
assente quando le monete spese sono esattamente le scommesse del round; non c'è riserva, la soglia di 294 sat si applica all'intera
quota delle commissioni e `fee_address` è letto dalla configurazione.
*Test:* `tests/unit/test_payout_builder.py::test_payout_deducts_fee_only_from_winner_share`, `tests/unit/test_payout_builder.py::test_payout_folds_excess_input_value_into_the_reserve_output`, `tests/unit/test_payout_builder.py::test_payout_leaves_a_dust_sized_excess_to_the_network_fee`, `tests/unit/test_payout_builder.py::test_payout_with_one_participant_at_the_smallest_viable_pool`
*Stato:* da implementare (B-95) · *Oggi:* `fee_address` è letto dalla
configurazione corrente al momento del pagamento, non fotografato
all'apertura.
- **[RND-24]** **Prima si scrive, poi si invia**: prima dell'invio il round
registra importo del vincitore (`winner_amount_sats`), commissioni
(`fee_amount_sats`), riserva per l'RBF (`bump_reserve_sats`, [RND-38]) e
@@ -464,9 +462,7 @@ del montepremi del round.
(TX-18, B-76, B-78); solo un abbandono libera il round per un nuovo
tentativo.
*Codice:* `app/rounds/scheduler.py:RoundScheduler._trigger_payout` ·
*Test:* `tests/unit/test_scheduler.py`
*Stato:* da implementare (B-79) · *Oggi:* tutto come descritto, tranne
`bump_reserve_sats`, che non esiste.
*Test:* `tests/unit/test_scheduler.py`, `tests/unit/test_scheduler.py::test_trigger_payout_always_carries_a_bump_reserve_to_the_reserve_address`
- **[RND-25]** Ogni tentativo che si ferma prima di un invio riuscito scrive
un evento `payout_failed` con il motivo: `electrum client not connected`,
`no fee_address configured`, `winner user not found`,
@@ -496,13 +492,8 @@ del montepremi del round.
riducendo l'uscita di riserva ([RND-38]); le uscite del vincitore e delle
commissioni non cambiano
([TX-11](transazioni-e-riconciliazione.md#rbf-accelerare-una-transazione-lenta)).
*Codice:* `app/tx/broadcast.py:bump_fee`
*Stato:* da implementare (B-79) · *Oggi:* l'RBF può ridurre solo il resto
verso il montepremi, che non esiste quando le monete spese sono
esattamente le scommesse del round: un pagamento bloccato nel
mempool non si può accelerare, e poiché il server lo conosce non viene
nemmeno abbandonato, quindi il round resta in `paying_out` e nessun nuovo
round si apre.
*Codice:* `app/tx/broadcast.py:bump_fee`, `app/tx/broadcast.py:_find_reducible_output` ·
*Test:* `tests/unit/test_broadcast.py::test_a_payout_is_bumped_out_of_its_reserve_output_and_nothing_else`, `tests/unit/test_broadcast.py::test_a_payout_can_be_bumped_repeatedly_while_the_reserve_lasts`
- **[RND-28]** Quando il pagamento è confermato il round passa a `closed` e
`closed_at` viene riscritto con l'orario della conferma: da lì parte la
pausa prima del round successivo. Il vincitore si vede accreditare la
@@ -516,9 +507,8 @@ del montepremi del round.
sat/vB è la costante `PAYOUT_BUMP_RESERVE_SAT_VB`. La riserva è tolta
dalla quota delle commissioni, mai da quella del vincitore; è ciò che
l'RBF riduce ([RND-27]).
*Codice:* `app/wallet/psbt_builder.py:build_payout_transaction`, `app/wallet/hd.py:derive_pool_key` ·
*Test:* —
*Stato:* da implementare (B-79) · *Oggi:* non esiste alcuna riserva.
*Codice:* `app/wallet/psbt_builder.py:build_payout_transaction`, `app/wallet/psbt_builder.py:payout_bump_reserve`, `app/wallet/hd.py:derive_pool_key` ·
*Test:* `tests/unit/test_payout_builder.py`, `tests/unit/test_scheduler.py::test_trigger_payout_always_carries_a_bump_reserve_to_the_reserve_address`
- **[RND-39]** **Verifica prima del pagamento**: prima di costruire il
pagamento, le uscite delle scommesse dei partecipanti vengono verificate
all'indirizzo del montepremi del round con il quorum dei server
@@ -564,11 +554,8 @@ del montepremi del round.
così un aumento della commissione di rete a round in corso non può rendere
impagabile il round (il pagamento legge `fee_rate_sat_vb` al momento della
costruzione).
*Codice:* `app/rounds/service.py:rounds_can_open`, `app/api/routes/admin.py` ·
*Test:* —
*Stato:* da implementare (B-79) · *Oggi:* `rounds_can_open` controlla solo
che `fee_address` non sia vuoto, e `PUT /admin/config` non fa verifiche di
pagabilità.
*Codice:* `app/rounds/service.py:payout_config_problem`, `app/rounds/service.py:rounds_can_open`, `app/api/routes/admin.py:update_config` ·
*Test:* `tests/unit/test_rounds_service.py::test_payout_config_problem_at_both_extremes`, `tests/unit/test_rounds_service.py::test_no_round_opens_while_the_config_could_not_pay_one`, `tests/unit/test_admin.py::test_config_refuses_a_change_that_would_make_a_round_unpayable`, `tests/unit/test_admin.py::test_a_fee_rate_raised_mid_round_cannot_make_the_round_unpayable`
## Tempi reali
+1 -1
View File
@@ -71,7 +71,7 @@ implementato il [piano delle correzioni](piano-correzioni.md).
tetto al fee rate (`MAX_FEE_RATE_SAT_VB`); abbandono dopo 6 ore per ciò che
sparisce dalla rete; ritentativo automatico del pagamento dopo un
abbandono.
- *Stato:* da chiudere con B-79 e B-87.
- *Stato:* da chiudere con B-87 (la riserva del pagamento, B-79, c'è).
- *Per chiuderlo:* ogni pagamento porta un'uscita di riserva che
l'accelerazione riduce ([ADR-17](adr/ADR-17-commissione-pagamento-esce-dalla-quota-vincitore.md), B-79); un aumento che lascerebbe un
resto sotto `DUST_LIMIT_SATS` elimina il resto e ne versa il valore in
+13 -15
View File
@@ -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
+5 -5
View File
@@ -616,9 +616,10 @@ con `docker compose logs app` ([architettura.md](architettura.md) ARC-09).
polvere) compare invece come errore di invio nello stesso messaggio.
**Causa.** L'RBF riduce solo il resto che torna al mittente; se il resto non
basta a coprire l'aumento, o la commissione è già al massimo (10.000 sat/vB),
non si può fare. **Un pagamento di norma non ha resto** (spende esattamente
gli UTXO del montepremi), quindi oggi un pagamento fermo nel mempool non si
può accelerare in nessun caso.
non si può fare. Un pagamento ha un'uscita di riserva
(B-79) che l'aumento riduce; la riserva è dimensionata per arrivare a circa
10 sat/vB, oltre la quale (o per un pagamento costruito prima di B-79, che
non ce l'ha) la transazione non si può accelerare.
**Cosa fare.** Nessuna procedura supportata. La transazione resta in attesa
alla commissione che ha: conferma quando il mempool la accetta, oppure, se
sparisce dal mempool, viene abbandonata dopo 6 ore ([OPS-16]). Se blocca un
@@ -627,8 +628,7 @@ con `docker compose logs app` ([architettura.md](architettura.md) ARC-09).
([OPS-23]); il sistema accrediterà l'uscita della nuova transazione come un
normale deposito. Per un pagamento non c'è un'alternativa supportata: la
chiave del montepremi non è esportabile ([OPS-24]).
Dopo B-79 ogni pagamento ha un'uscita di riserva che l'RBF può ridurre;
dopo B-87 un aumento che lascerebbe un resto sotto la soglia di polvere
Dopo B-87 un aumento che lascerebbe un resto sotto la soglia di polvere
toglie il resto invece di fallire, e ogni fallimento scrive
`rbf_bump_failed`.
*Codice:* `app/tx/broadcast.py:bump_fee`, `app/tx/broadcast.py:RbfBumper._tick` ·
+3 -3
View File
@@ -227,7 +227,7 @@ cifrava di nuovo il file sbagliato (vedi [operativita.md](operativita.md)).
| B-76 | Un invio ambiguo (timeout, connessione caduta) viene annullato come se fosse rifiutato | Critica | *da registrare* | `test_broadcast_outcome.py`, `test_electrum_client.py` | TX, BET, WDR |
| B-77 | Si ricorda solo l'ultimo txid di una transazione accelerata, e la sostituzione viene inviata prima di essere salvata | Critica | *da registrare* | `test_tx_versions.py`, `test_migrations.py`, `test_broadcast.py` | TX |
| B-78 | Una transazione viene abbandonata sulla base del silenzio di un solo server | Critica | *da registrare* | `test_reconcile_proof.py`, `test_reconcile.py`, `test_pause_reason.py`, `test_migrations.py` | TX, ELC |
| B-79 | Il pagamento di norma non ha resto, quindi non si può accelerare e può bloccare la lotteria | Critica | **aperto** | — | RND, TX |
| B-79 | Il pagamento di norma non ha resto, quindi non si può accelerare e può bloccare la lotteria | Critica | *da registrare* | `test_payout_builder.py`, `test_broadcast.py`, `test_scheduler.py`, `test_rounds_service.py`, `test_admin.py`, `test_migrations.py` | RND, TX |
| B-80 | Un solo indirizzo del montepremi per tutti i round | Critica | *da registrare* | `test_round_pool_address.py`, `test_pending_address.py`, `test_scheduler.py` | RND, BET, WDR |
| B-81 | `drawing` resta bloccato dopo qualsiasi eccezione, non solo dopo un riavvio | Critica | *da registrare* | `test_draw_proof.py` | RND |
| B-82 | Un deposito annullato da una riorganizzazione dopo la scommessa rende il round impagabile; coinbase accreditate a 1 conferma | Critica | **aperto** | — | DEP, RND |
@@ -252,9 +252,9 @@ cifrava di nuovo il file sbagliato (vedi [operativita.md](operativita.md)).
- **Corretti:** B-01 … B-70, tranne B-16, che è chiuso nella sostanza (il
crash è eliminato e `/guida` serve un segnaposto) ma lascia la guida vera
come limite accettato; dalla revisione del 2026-09-28: B-88, B-75, B-97, B-80, B-84, B-76, B-77, B-78, B-83, B-74, B-85, B-86, B-73, B-81.
come limite accettato; dalla revisione del 2026-09-28: B-88, B-75, B-97, B-80, B-84, B-76, B-77, B-78, B-83, B-74, B-85, B-86, B-73, B-81, B-79.
- **Aperti:** B-71, B-72 (gravità bassa, operativi o di documentazione) e
B-73 … B-98 dalla revisione del 2026-09-28 tranne i corretti (2 critici,
B-73 … B-98 dalla revisione del 2026-09-28 tranne i corretti (1 critico,
4 alti, 6 medi), da implementare nell'ordine di
[piano-correzioni.md](piano-correzioni.md).
- **Prossimo numero libero:** `B-99`.
+3 -7
View File
@@ -244,13 +244,9 @@ sostituibilità fin dal primo invio.
mai abbandonata.
*Codice:* `app/tx/broadcast.py:bump_fee`, `app/tx/broadcast.py:_find_change_output` ·
*Test:* `tests/unit/test_broadcast.py`
*Stato:* da implementare (B-79, B-87) · *Oggi:* per il pagamento si cerca
un resto verso l'indirizzo del montepremi, che esiste solo se le monete
scelte superano vincitore più commissioni di almeno 294 sat: quando il
montepremi contiene esattamente le scommesse del round il pagamento le
spende tutte per esattamente `pool_amount_sats`, non ha resto e non è
accelerabile; per scommesse e prelievi basta che il resto superi
l'aumento, così un resto di 294 sat diventa 153 sat, sotto la soglia di
*Stato:* da implementare (B-87) · *Oggi:* il pagamento si accelera già
dall'uscita di riserva (B-79), ma per ogni transazione basta che l'uscita
superi l'aumento: un resto di 294 sat diventa 153 sat, sotto la soglia di
dust, e la rete rifiuta la sostituzione a ogni giro.
- **[TX-12]** **Prima si scrive la sostituzione, poi si invia.** La
sostituzione firmata viene registrata come candidata in