Keep the fee-rate ceiling below the node's maxfeerate (B-100)

MAX_FEE_RATE_SAT_VB was exactly sendrawtransaction's default maxfeerate
(10 000 sat/vB); with vsize estimated upwards a rate at the ceiling would be
refused. Now 5 000.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-18 00:32:24 +02:00
1 parent 2c0e71a74c
commit cef05943f9
5 files changed
+25 -25

No files matched your search

+4 -2
View File
@@ -30,8 +30,10 @@ DUST_LIMIT_SATS = 294
# reason MIN_PASSWORD_LENGTH is shared in auth/security.py) and tx/broadcast.py's RBF
# bump escalation, which refuses to bump a pending_transaction past this rate (B-32) —
# without a ceiling, a stuck transaction's fee climbed by 1 sat/vB every bump forever,
# eating further and further into the sender's change with no limit.
MAX_FEE_RATE_SAT_VB = 10_000
# eating further and further into the sender's change with no limit. B-100: half of the node's own
# `sendrawtransaction` maxfeerate (0.1 PLM/kvB = 10 000 sat/vB, palladiumcore rpc/rawtransaction.cpp):
# vsize is estimated upwards, so a rate sitting exactly on the node's limit would be refused.
MAX_FEE_RATE_SAT_VB = 5_000
# Ceiling on how many UTXOs one *user* transaction (bet, withdrawal) may spend (B-48).
# Every extra input costs ~68 vbytes of fee, and that fee comes out of the amount being
+4 -4
View File
@@ -50,7 +50,7 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
| `GET` | `/admin/pending-transactions` | admin | [API-38](#get-adminpending-transactions) |
| `GET` | `/admin/bug-reports` | admin | [API-39](#get-adminbug-reports) |
| `POST` | `/admin/bug-reports/{report_id}/status` | admin | [API-40](#post-adminbug-reportsreport_idstatus) |
| `GET` | `/admin/electrum` | admin | [API-47](#get-adminelectrum) — *da implementare (B-86, B-92)* |
| `GET` | `/admin/electrum` | admin | [API-47](#get-adminelectrum) — *da implementare (B-92)* |
| `GET` | `/health` | nessuna | [API-41](#get-health) |
| `GET` | `/health/ready` | nessuna | [API-46](#get-healthready) — *da implementare (B-92)* |
| `GET` | `/`, `/admin`, `/guida`, `/report-bug` | nessuna | [API-42](#pagine-e-file-statici) |
@@ -770,7 +770,7 @@ dei parametri operativi in
| `bet_amount_sats` | > 0 e ≤ 10 000 000 000 000 (100 000 PLM) |
| `round_duration_seconds` | 30 – 604 800 (7 giorni) |
| `round_cooldown_seconds` | 0 – 86 400 |
| `fee_rate_sat_vb` | 1 – 10 000 (`MAX_FEE_RATE_SAT_VB`) |
| `fee_rate_sat_vb` | 1 – 5 000 (`MAX_FEE_RATE_SAT_VB`) |
| `rbf_timeout_seconds` | 60 – 604 800 |
| `draw_animation_seconds` | 0 – 600 |
| `deposit_confirmations` | 1 – 100 |
@@ -941,7 +941,7 @@ dei parametri operativi in
ferma, [connessione-electrum.md](connessione-electrum.md) ELC-21).
*Codice:* `app/api/routes/admin.py`, `app/electrum/listener.py:ElectrumListener.current_endpoint` ·
*Test:* —
*Stato:* da implementare (B-86, B-92) · *Oggi:* l'endpoint non esiste;
*Stato:* da implementare (B-92) · *Oggi:* l'endpoint non esiste;
`/admin` mostra solo l'altezza della catena letta da `GET /rounds/current`.
#### GET /admin/bug-reports
@@ -986,7 +986,7 @@ dei parametri operativi in
**tutte** queste condizioni sono vere, altrimenti `503` con lo stesso corpo e
`"status": "not_ready"`:
- la connessione Electrum è attiva (`listener.client` non nullo);
- l'ultimo header è stato accettato da meno di 360 secondi
- l'ultimo header è stato accettato da meno di 1800 secondi
([connessione-electrum.md](connessione-electrum.md) ELC-29);
- lo scheduler dei round ha battuto da meno di 30 secondi (il battito si
aggiorna a ogni giro e a ogni iterazione dell'attesa del blocco di
+13 -15
View File
@@ -53,8 +53,8 @@ leggono dalla rete, non da una tabella:
| Riserva del pool (il "pool" unico di oggi) | `m/84'/746'/0'/1/0` | nessuna tabella; `app/wallet/hd.py:derive_pool_key` |
| Montepremi di un round | `m/84'/746'/0'/2/<round_id>` | `rounds.pool_address` ([DAT-60]) |
Il diagramma descrive lo schema **atteso**; le colonne e la tabella marcate
"da implementare" non esistono ancora (vedi le tabelle di dettaglio).
Il diagramma descrive lo schema **atteso**; ciò che non esiste ancora è
marcato *da implementare* nelle tabelle di dettaglio.
```mermaid
erDiagram
@@ -103,11 +103,11 @@ erDiagram
bigint max_round_pool_sats "snapshot"
int draw_confirmations "snapshot"
string pool_address
string server_seed_enc "cifrato fino alla rivelazione (da implementare)"
string server_seed "in chiaro solo dopo la rivelazione (da implementare)"
string server_seed_hash "da implementare"
string participants_hash "da implementare"
int draw_height "da implementare"
string server_seed_enc "cifrato fino alla rivelazione"
string server_seed "in chiaro solo dopo la rivelazione"
string server_seed_hash
string participants_hash
int draw_height
datetime drawing_started_at
int draw_block_height
string draw_block_hash
@@ -118,7 +118,7 @@ erDiagram
bigint fee_amount_sats
bigint bump_reserve_sats
string payout_txid
datetime payout_blocked_at "da implementare"
datetime payout_blocked_at
}
round_participants {
int id PK
@@ -126,10 +126,10 @@ erDiagram
int user_id FK
bigint bet_amount_sats
string bet_txid
int bet_vout "da implementare"
int bet_vout
datetime broadcast_at
datetime confirmed_at
int draw_position "UK con round_id (da implementare)"
int draw_position "UK con round_id"
string status "building broadcast confirmed reorged_out"
}
round_config {
@@ -142,10 +142,10 @@ erDiagram
int fee_rate_sat_vb
int rbf_timeout_seconds
int deposit_confirmations
int draw_confirmations "da implementare"
int draw_confirmations
bigint max_round_pool_sats
bool paused
string paused_reason "da implementare"
string paused_reason
datetime updated_at
}
pending_transactions {
@@ -709,8 +709,6 @@ Una scommessa: un utente in un round.
`app/tx/reconcile.py:_abandon` · `app/bets/confirmation.py:_on_bet_confirmed` ·
*Test:* `tests/unit/test_bets.py`, `tests/unit/test_confirmation.py`,
`tests/unit/test_reconcile.py`, `tests/unit/test_pool_verification.py`
*Stato:* da implementare (B-76) · *Oggi:* qualunque eccezione del broadcast
cancella la riga come un rifiuto.
- **[DAT-25]** `broadcast_at` è l'istante della fase 1 di `place_bet`, appena
prima del broadcast, e non viene mai riscritto (nemmeno da un bump RBF). Non
@@ -774,7 +772,7 @@ senza riavviare (vedi [limiti-noti.md](limiti-noti.md)).
| `round_duration_seconds` | Integer | `600` | 30 s – 7 giorni | Durata del round | Dal round **successivo** ([RND-05](ciclo-di-vita-del-round.md)) |
| `round_cooldown_seconds` | Integer | `30` | 0 s – 24 h | Pausa dopo la chiusura | Dal round **successivo**: la pausa dopo il round in corso è quella copiata alla sua apertura ([RND-05](ciclo-di-vita-del-round.md)) |
| `draw_animation_seconds` | Integer | `20` | 0 – 600 s | Durata minima dell'animazione dell'estrazione nel frontend; non condiziona l'estrazione vera | Subito (letto da `GET /rounds/current`) |
| `fee_rate_sat_vb` | Integer | `1` | 1 – `MAX_FEE_RATE_SAT_VB` (10 000) | Commissione di rete delle nuove transazioni | Subito, per le transazioni costruite dopo; quelle in volo tengono il proprio `fee_rate_sat_vb` |
| `fee_rate_sat_vb` | Integer | `1` | 1 – `MAX_FEE_RATE_SAT_VB` (5 000) | Commissione di rete delle nuove transazioni | Subito, per le transazioni costruite dopo; quelle in volo tengono il proprio `fee_rate_sat_vb` |
| `rbf_timeout_seconds` | Integer | `900` | 60 s – 7 giorni | Dopo quanto senza conferme si fa un bump RBF | Subito, anche per le transazioni già in volo (il bumper lo rilegge a ogni giro) |
| `deposit_confirmations` | Integer | `6` | 1 – 100 | Conferme richieste per accreditare un deposito ([DAT-62]) | Subito, per gli accrediti successivi; gli output già accreditati restano |
| `draw_confirmations` | Integer | `3` | 1 – 20 | Conferme richieste sul blocco di estrazione ([DAT-62]) | Dal round **successivo**, copiato all'apertura ([DAT-61]) |
+2 -2
View File
@@ -618,7 +618,7 @@ con `docker compose logs app` ([architettura.md](architettura.md) ARC-09).
e il suo valore va in commissione, e quella sostituzione è l'ultima
(`change_too_small` ai tentativi successivi). Non si può fare nemmeno se
l'uscita non esiste o non basta all'aumento minimo, o se la commissione è già
al massimo (10.000 sat/vB, `max_fee_rate`); `replacement_rejected` è una
al massimo (5.000 sat/vB, `max_fee_rate`); `replacement_rejected` è una
sostituzione che la rete ha rifiutato esplicitamente. 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
@@ -894,7 +894,7 @@ proposito.
| 6 | Riorganizzazione di 1 blocco | Solo su una rete di prova con due minatori, se disponibile | La punta segue il ramo nuovo senza chiudere la sessione; `chain_reorg_detected` con profondità 1; nessuna pausa. Con profondità 2: pausa con motivo `reorg` (B-85, B-74) |
| 7 | Cronologia molto lunga | Indirizzo con risposta oltre 64 KiB (per esempio mille piccoli invii a un indirizzo di prova) | La connessione resta aperta (limite di 16 MiB); le uscite sotto `DUST_LIMIT_SATS` non vengono accreditate (B-75) |
| 8 | Tutte le riserve giù | Bloccare con il firewall ogni server di `ELECTRUM_FALLBACK_SERVERS` | Accrediti rinviati, estrazione in attesa, nessuna spesa esterna registrata, nessun denaro mosso; al ritorno dei server tutto riprende. Un avvio con meno di 2 riserve è rifiutato (B-84) |
| 9 | Server connesso ma fermo | Un server Electrum di prova che smette di seguire la catena | Dopo 360 secondi senza avanzamento il listener passa a un server con una punta più alta (B-86); `GET /health/ready` risponde 503 nel frattempo (B-92) |
| 9 | Server connesso ma fermo | Un server Electrum di prova che smette di seguire la catena | Dopo 360 secondi senza avanzamento il listener passa a un server con una punta più alta (B-86); `GET /health/ready` risponde 503 solo oltre 1800 secondi senza header (B-92) |
| 10 | Salto dell'orologio | Portare avanti l'orologio dell'host di 7 ore con transazioni `pending` | Nessuna transazione abbandonata per il solo tempo trascorso (B-78) |
| 11 | Disco pieno | Riempire il filesystem di `./data/db` durante l'attività | Nessuna transazione trasmessa senza la sua riga salvata prima; scommesse e prelievi falliscono prima dell'invio; liberato lo spazio, il sistema riprende senza interventi sul database |
| 12 | Ripristino da backup | Backup, poi un round completo e una registrazione, poi [OPS-07] | Avvio in pausa con `START_PAUSED=true`; `restore_gap_detected` e registrazioni rifiutate; il round rigiocato non viene pagato una seconda volta (B-90, B-80, B-82) |
+2 -2
View File
@@ -217,10 +217,10 @@ sostituibilità fin dal primo invio.
*Codice:* `app/tx/broadcast.py:RbfBumper._tick`, `app/tx/broadcast.py:should_bump` ·
*Test:* `tests/unit/test_broadcast.py`
- **[TX-10]** La nuova commissione punta a 1 sat/vB in più della tariffa
registrata (`_FEE_RATE_INCREMENT`), senza superare 10.000 sat/vB
registrata (`_FEE_RATE_INCREMENT`), senza superare 5.000 sat/vB
(`MAX_FEE_RATE_SAT_VB`); l'aumento assoluto non è mai inferiore alla
dimensione stimata della transazione per 1 sat/vB, il minimo richiesto da
BIP125 per una sostituzione. Una riga già a 10.000 sat/vB non viene più
BIP125 per una sostituzione. Una riga già a 5.000 sat/vB non viene più
accelerata. Gli importi delle monete spese sono letti dalle transazioni
precedenti come interi in satoshi.
*Codice:* `app/tx/broadcast.py:bump_fee`, `app/tx/broadcast.py:_prevout_amount` ·