diff --git a/app/wallet/psbt_builder.py b/app/wallet/psbt_builder.py index efe70cd..2b5dbd8 100644 --- a/app/wallet/psbt_builder.py +++ b/app/wallet/psbt_builder.py @@ -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 diff --git a/docs/api.md b/docs/api.md index 3b86f4a..6b10de9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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 diff --git a/docs/modello-dati.md b/docs/modello-dati.md index 849605c..1cd5100 100644 --- a/docs/modello-dati.md +++ b/docs/modello-dati.md @@ -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/` | `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]) | diff --git a/docs/operativita.md b/docs/operativita.md index c4aef1e..163ac7e 100644 --- a/docs/operativita.md +++ b/docs/operativita.md @@ -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) | diff --git a/docs/transazioni-e-riconciliazione.md b/docs/transazioni-e-riconciliazione.md index 6b7a576..7dcefd9 100644 --- a/docs/transazioni-e-riconciliazione.md +++ b/docs/transazioni-e-riconciliazione.md @@ -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` ·