Track every version of a bumped transaction and write the RBF replacement before broadcasting it (B-77, TX-12, TX-13, TX-19, TX-21, DAT-13, DAT-14, DAT-34, DAT-44, DAT-45, DAT-70, DAT-71)
The poller and the reconciler matched current_txid only. If an earlier version of a bumped transaction confirmed it was never seen, and after 6h the row was abandoned: a confirmed bet's participant deleted, a sent withdrawal marked failed, a paid payout rebuilt. bump_fee also broadcast in phase 2 and persisted in phase 3 unconditionally, so a crash or a `database is locked` in between left a transaction on the network that the DB did not know, and an abandon or a confirmation in the meantime was overwritten. - New table pending_transaction_txids (migration a3f5c2d9b7e1; txid unique, index on the owning row; existing rows backfilled with current_txid + raw and the txid their last bump replaced, raw_hex empty). Written with the row by place_bet, request_withdrawal and _trigger_payout; removed with a bet row that a verified refusal deletes (DAT-38). - bump_fee in four phases: read, build and sign, commit the replacement as a candidate version, broadcast (verdict of B-76, fanned out when accepted; an explicit refusal raises RbfError and leaves the row), then switch the row over with `UPDATE ... WHERE status='pending' AND current_txid=<replaced>`. Zero rows matched is a logged conflict: nothing is overwritten and the candidate stays recorded. An uncertain broadcast adopts the replacement. A retry rebuilds the same candidate without tripping the unique txid. - poll_once and reconcile_once look for every txid of a row. When the version found is not the current one, broadcast.adopt_version makes it current (raw tx included) and retargets spent_txid, bet_txid, withdrawals.txid and payout_txid as a bump does, before the confirmation handler runs. _release_inputs frees inputs claimed by any version of the row. - RbfBumper takes the listener as `network`. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2b6ddb019b
commit
f936d6660c
23 files changed
+836
-130
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# ADR-31 — Tutto ciò che spende denaro si scrive prima di inviarlo
|
||||
|
||||
- **Stato:** Accettata — implementazione da completare (B-77)
|
||||
- **Stato:** Accettata
|
||||
- **Area:** Denaro in movimento
|
||||
- **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*.
|
||||
|
||||
@@ -20,5 +20,4 @@ riconciliatore e risponde **202** ([ADR-51](ADR-51-dubbio-liberano-mai-fondi.md)
|
||||
|
||||
*Codice:* `app/bets/service.py:place_bet` · `app/withdrawals/service.py:request_withdrawal` · `app/rounds/scheduler.py:RoundScheduler._trigger_payout` · `app/tx/broadcast.py:bump_fee` · `app/tx/reconcile.py:reconcile_once` ·
|
||||
*Test:* `tests/unit/test_bets.py::test_bet_is_persisted_before_it_is_broadcast`, `tests/unit/test_broadcast_outcome.py`, `tests/unit/test_scheduler.py::test_trigger_payout_persists_before_broadcasting`, `tests/unit/test_reconcile.py`
|
||||
*Stato:* da implementare (B-77), per le sostituzioni RBF · *Oggi:*
|
||||
`bump_fee` invia la sostituzione prima di salvarla.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ADR-51 — Nel dubbio non si liberano mai i fondi
|
||||
|
||||
- **Stato:** Accettata — implementazione da completare (B-77, B-78)
|
||||
- **Stato:** Accettata — implementazione da completare (B-78)
|
||||
- **Area:** Denaro in movimento
|
||||
- **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*.
|
||||
|
||||
@@ -37,7 +37,7 @@ pausa la lotteria ([ADR-28](ADR-28-pausa-manutenzione-ferma-round-successivo.md)
|
||||
|
||||
*Codice:* `app/tx/reconcile.py:reconcile_once` · `app/tx/reconcile.py:_is_due` · `app/bets/service.py:place_bet` · `app/withdrawals/service.py:request_withdrawal` · `app/tx/confirmation.py:poll_once` · `app/tx/broadcast.py:bump_fee` ·
|
||||
*Test:* `tests/unit/test_broadcast_outcome.py`
|
||||
*Stato:* da implementare (B-77, B-78) · *Oggi:* l'abbandono avviene
|
||||
quando la cronologia del solo server attivo non contiene il txid corrente,
|
||||
dopo 120s per `building` e 6 ore per `pending`; si segue solo l'ultimo txid;
|
||||
le righe `failed` non sono più controllate.
|
||||
*Stato:* da implementare (B-78) · *Oggi:* l'abbandono avviene
|
||||
quando la cronologia del solo server attivo non contiene alcun txid della
|
||||
riga, dopo 120s per `building` e 6 ore per `pending`; le righe `failed` non
|
||||
sono più controllate.
|
||||
@@ -564,8 +564,8 @@ implementare» non esistono ancora nel codice (vedi [ARC-60] e [ARC-22]).
|
||||
([LIM-01](limiti-noti.md#robustezza-del-ciclo-di-vita)).
|
||||
- **[ARC-28]** **`ConfirmationPoller`** (10s), **`RbfBumper`** (30s) e
|
||||
**`PendingTransactionReconciler`** (al primo giro subito, poi ogni 120s)
|
||||
lavorano sulla tabella `pending_transactions` (e, dal piano, sulla tabella
|
||||
di tutti i txid di ogni riga, B-77); il riconciliatore parte subito perché
|
||||
lavorano sulla tabella `pending_transactions` (e sulla tabella
|
||||
di tutti i txid di ogni riga, `pending_transaction_txids`); il riconciliatore parte subito perché
|
||||
un crash durante un invio lascia proprio una riga `building` da risolvere
|
||||
al riavvio. Regole in
|
||||
[transazioni-e-riconciliazione.md](transazioni-e-riconciliazione.md).
|
||||
|
||||
@@ -349,8 +349,7 @@ L'**invio** di una transazione non è una corroborazione ma usa gli stessi
|
||||
server: dopo che il server attivo l'ha accettata, la transazione grezza viene
|
||||
inviata anche a ogni server di riserva, al meglio. La regola è in
|
||||
[transazioni-e-riconciliazione.md](transazioni-e-riconciliazione.md) (B-76,
|
||||
B-83); oggi l'invio va anche a tutti gli altri server, salvo la sostituzione
|
||||
RBF di `bump_fee` (B-77).
|
||||
B-83).
|
||||
|
||||
## Riconnessione
|
||||
|
||||
|
||||
+15
-40
@@ -42,8 +42,7 @@ Cosa **non** c'è qui, e dove trovarlo:
|
||||
|
||||
## Diagramma delle relazioni
|
||||
|
||||
Dieci tabelle applicative nello schema atteso (nove oggi: manca
|
||||
`pending_transaction_txids`, B-77). `round_config` non ha relazioni: è una
|
||||
Dieci tabelle applicative. `round_config` non ha relazioni: è una
|
||||
riga singola di parametri. Gli indirizzi della piattaforma che non
|
||||
appartengono a un utente sono derivati dalla master key, e i loro UTXO si
|
||||
leggono dalla rete, non da una tabella:
|
||||
@@ -67,7 +66,7 @@ erDiagram
|
||||
rounds |o--o{ pending_transactions : "bet / payout"
|
||||
withdrawals |o--o{ pending_transactions : "withdrawal"
|
||||
users |o--o{ pending_transactions : "bet / withdrawal"
|
||||
pending_transactions ||--|{ pending_transaction_txids : "ogni versione (da implementare)"
|
||||
pending_transactions ||--|{ pending_transaction_txids : "ogni versione"
|
||||
users |o--o{ bug_reports : "segnala"
|
||||
users |o--o{ audit_log : "riguarda"
|
||||
rounds |o--o{ audit_log : "riguarda"
|
||||
@@ -233,7 +232,7 @@ erDiagram
|
||||
([DAT-52]). Nessuna chiave esterna ha `ON DELETE`. Le sole cancellazioni
|
||||
fisiche nel codice sono quelle di una scommessa fallita (la riga
|
||||
`round_participants` e la sua `pending_transactions`, con le sue versioni in
|
||||
`pending_transaction_txids` nello schema atteso,
|
||||
`pending_transaction_txids`,
|
||||
[DAT-24], [DAT-38], [DAT-70]); nessun'altra tabella perde righe, quindi i riferimenti
|
||||
restano validi per costruzione, non per vincolo.
|
||||
*Codice:* `app/db/base.py:_register_sqlite_pragmas` ·
|
||||
@@ -402,9 +401,7 @@ del database sono tutti uguali, perché arrivano tutti dallo stesso percorso
|
||||
stesso output risulta impegnato da un'altra transazione, quella
|
||||
rivendicazione è quella valida e non va toccata.
|
||||
*Codice:* `app/tx/reconcile.py:_release_inputs` ·
|
||||
*Test:* `tests/unit/test_reconcile.py`
|
||||
*Stato:* da implementare (B-77), per le versioni precedenti · *Oggi:* il
|
||||
confronto è solo con `current_txid`.
|
||||
*Test:* `tests/unit/test_reconcile.py`, `tests/unit/test_tx_versions.py`
|
||||
|
||||
- **[DAT-14]** Per ogni output impegnato, `spent_txid` è sempre il txid
|
||||
**corrente** della transazione che lo spende, cioè il `current_txid` della sua
|
||||
@@ -419,9 +416,7 @@ del database sono tutti uguali, perché arrivano tutti dallo stesso percorso
|
||||
*Codice:* `app/tx/broadcast.py:bump_fee` ·
|
||||
`app/tx/broadcast.py:_retarget_txid_references` ·
|
||||
`app/tx/confirmation.py:poll_once` ·
|
||||
*Test:* `tests/unit/test_broadcast.py`
|
||||
*Stato:* da implementare (B-77) · *Oggi:* solo il bump riallinea; la
|
||||
conferma di una versione precedente non viene riconosciuta.
|
||||
*Test:* `tests/unit/test_broadcast.py`, `tests/unit/test_tx_versions.py`
|
||||
|
||||
### `rounds`
|
||||
|
||||
@@ -904,7 +899,7 @@ riconciliatore (vedi
|
||||
| `broadcast_at` | DateTime | no | `utcnow` | Prima trasmissione, vedi [DAT-35] | ORM, alla creazione |
|
||||
| `last_broadcast_at` | DateTime | no | `utcnow` | Ultima trasmissione, vedi [DAT-35] | ORM; `bump_fee` |
|
||||
| `status` | String(16) | no | `"pending"` (mai usato: si crea sempre `building`) | Vedi [DAT-33] | vedi diagramma |
|
||||
| `replaced_by_txid` | String(64) | sì | `NULL` | Il txid **sostituito** dall'ultimo bump (nonostante il nome, punta all'indietro). Nello schema atteso la cronologia completa è in `pending_transaction_txids` ([DAT-70]) e questa colonna resta solo informativa | `bump_fee` |
|
||||
| `replaced_by_txid` | String(64) | sì | `NULL` | Il txid **sostituito** dall'ultimo bump (nonostante il nome, punta all'indietro). La cronologia completa è in `pending_transaction_txids` ([DAT-70]) e questa colonna resta solo informativa | `bump_fee` |
|
||||
| `attempt_count` | Integer | no | `1` | 1 + numero di bump riusciti | `bump_fee` |
|
||||
| `failure_reason` | String(128) | sì | `NULL` | Perché la transazione è fallita; solo con `status = failed` | `_abandon`; `_release_failed_withdrawal` |
|
||||
| `failed_at` | DateTime | sì | `NULL` | Istante del passaggio a `failed`; da lì si contano i 14 giorni di sorveglianza ([DAT-72]) — *da implementare (B-78)* | `_abandon`; `_release_failed_withdrawal` |
|
||||
@@ -995,12 +990,7 @@ Le funzioni citate sono `app/bets/service.py:place_bet`,
|
||||
|
||||
`fee_rate_sat_vb` registra la commissione effettivamente pagata dalla nuova
|
||||
versione, non quella obiettivo.
|
||||
*Codice:* `app/tx/broadcast.py:bump_fee` · *Test:* `tests/unit/test_broadcast.py`
|
||||
*Stato:* da implementare (B-77) · *Oggi:* la nuova versione è trasmessa
|
||||
nella fase 2 prima di esistere nel database, e la fase 3 riscrive la riga
|
||||
**senza ricontrollarne** stato e `current_txid`: un abbandono o una
|
||||
conferma avvenuti nel frattempo vengono sovrascritti. `replaced_by_txid`
|
||||
conserva solo il txid immediatamente precedente.
|
||||
*Codice:* `app/tx/broadcast.py:bump_fee` · *Test:* `tests/unit/test_broadcast.py`, `tests/unit/test_tx_versions.py`
|
||||
|
||||
- **[DAT-35]** `broadcast_at` è la **prima** trasmissione e non viene mai
|
||||
riscritta: è il riferimento da cui il riconciliatore misura i propri tempi
|
||||
@@ -1062,7 +1052,7 @@ Le funzioni citate sono `app/bets/service.py:place_bet`,
|
||||
|
||||
### `pending_transaction_txids`
|
||||
|
||||
*Tabella da implementare (B-77).* Ogni versione che una riga
|
||||
Ogni versione che una riga
|
||||
`pending_transactions` abbia mai avuto: la prima, ogni sostituzione RBF
|
||||
trasmessa e ogni candidata scritta prima di un bump. Serve perché una
|
||||
qualunque di queste versioni può essere quella che la rete conferma.
|
||||
@@ -1086,12 +1076,9 @@ qualunque di queste versioni può essere quella che la rete conferma.
|
||||
`current_txid`: la conferma di uno qualunque conferma la riga ([DAT-14]).
|
||||
La riga `bet` cancellata da `_release_failed_bet` ([DAT-38]) porta con sé
|
||||
le proprie versioni, nella stessa transazione.
|
||||
*Codice:* `app/db/models.py` · `app/tx/broadcast.py:bump_fee` ·
|
||||
*Codice:* `app/db/models.py:PendingTransactionTxid` · `app/tx/versions.py` · `app/tx/broadcast.py:bump_fee` ·
|
||||
`app/tx/confirmation.py:poll_once` · `app/tx/reconcile.py:reconcile_once` ·
|
||||
*Test:* —
|
||||
*Stato:* da implementare (B-77) · *Oggi:* la tabella non esiste; poller e
|
||||
riconciliatore confrontano solo `current_txid`, e `replaced_by_txid`
|
||||
ricorda un solo passo indietro.
|
||||
*Test:* `tests/unit/test_tx_versions.py`, `tests/unit/test_migrations.py`
|
||||
|
||||
- **[DAT-71]** Una versione **candidata** è una riga di questa tabella il cui
|
||||
txid non è (ancora) `current_txid`: `bump_fee` l'ha scritta ma la fase 3 non
|
||||
@@ -1102,9 +1089,7 @@ qualunque di queste versioni può essere quella che la rete conferma.
|
||||
Il riconciliatore, quando ritrasmette, usa il `raw_tx_hex` corrente, non
|
||||
quello di una candidata.
|
||||
*Codice:* `app/tx/broadcast.py:bump_fee` · `app/tx/reconcile.py:reconcile_once` ·
|
||||
*Test:* —
|
||||
*Stato:* da implementare (B-77) · *Oggi:* non esistono candidate: la
|
||||
sostituzione esiste nel database solo dopo il suo broadcast riuscito.
|
||||
*Test:* `tests/unit/test_tx_versions.py`
|
||||
|
||||
### `withdrawals`
|
||||
|
||||
@@ -1240,8 +1225,8 @@ Tabella di servizio di Alembic: una riga con la revisione applicata
|
||||
| `ix_rounds_single_active` | `rounds` | `UNIQUE ((1)) WHERE status IN ('open','closing','drawing','paying_out')` | al massimo un round attivo, [DAT-17] (B-09) |
|
||||
| unico (`round_id`, `user_id`) | `round_participants` | `UNIQUE` | una scommessa per utente per round, [DAT-23] |
|
||||
| unico (`round_id`, `draw_position`) | `round_participants` | `UNIQUE` (i `NULL` non collidono) | una posizione di estrazione per partecipante, [DAT-66] — *da implementare (B-81)* |
|
||||
| unico `txid` | `pending_transaction_txids` | `UNIQUE` | un txid appartiene a una sola transazione, [DAT-70] — *da implementare (B-77)* |
|
||||
| `ix_pending_transaction_txids_pending_transaction_id` | `pending_transaction_txids` | indice | versioni di una transazione — *da implementare (B-77)* |
|
||||
| unico `txid` | `pending_transaction_txids` | `UNIQUE` | un txid appartiene a una sola transazione, [DAT-70] |
|
||||
| `ix_pending_transaction_txids_pending_transaction_id` | `pending_transaction_txids` | indice | versioni di una transazione |
|
||||
| `ix_round_participants_round_id` | `round_participants` | indice | partecipanti di un round |
|
||||
| `ix_round_participants_user_id` | `round_participants` | indice | round di un utente (`last-round-result`) |
|
||||
| `ix_withdrawals_user_id` | `withdrawals` | indice | prelievi di un utente |
|
||||
@@ -1267,12 +1252,7 @@ letti con una scansione completa.
|
||||
`app/withdrawals/service.py:request_withdrawal` ·
|
||||
`app/rounds/scheduler.py:RoundScheduler._trigger_payout` ·
|
||||
`app/tx/broadcast.py:bump_fee` ·
|
||||
*Test:* `tests/unit/test_bets.py`, `tests/unit/test_scheduler.py`
|
||||
*Stato:* da implementare (B-77), per le sostituzioni RBF · *Oggi:*
|
||||
l'invariante vale per la prima versione; una sostituzione RBF viene
|
||||
trasmessa nella fase 2 di `bump_fee` e scritta nel database solo nella
|
||||
fase 3, quindi un arresto o un `database is locked` fra le due lascia sulla
|
||||
rete una transazione che il database non conosce.
|
||||
*Test:* `tests/unit/test_bets.py`, `tests/unit/test_scheduler.py`, `tests/unit/test_tx_versions.py`
|
||||
|
||||
- **[DAT-45]** **Ogni versione è riconoscibile, una sola è corrente.** Per ogni
|
||||
transazione non terminale, `pending_transactions.current_txid` coincide con
|
||||
@@ -1287,12 +1267,7 @@ letti con una scansione completa.
|
||||
txid.
|
||||
*Codice:* `app/tx/broadcast.py:_retarget_txid_references` ·
|
||||
`app/tx/broadcast.py:bump_fee` · `app/tx/confirmation.py:poll_once` ·
|
||||
*Test:* `tests/unit/test_broadcast.py`
|
||||
*Stato:* da implementare (B-77) · *Oggi:* l'aggiornamento dei riferimenti
|
||||
avviene in una transazione del database separata dal broadcast e non
|
||||
condizionata, e i txid precedenti sono dimenticati (salvo l'ultimo, in
|
||||
`replaced_by_txid`, che nessuno legge): se conferma una versione
|
||||
precedente, poller e riconciliatore non la riconoscono.
|
||||
*Test:* `tests/unit/test_broadcast.py`, `tests/unit/test_tx_versions.py`
|
||||
|
||||
- **[DAT-46]** **Il montepremi di ogni round si conserva, separato dagli
|
||||
altri.** Per un round pagato, con i soli partecipanti rimasti `confirmed`
|
||||
|
||||
+2
-2
@@ -615,8 +615,8 @@ con `docker compose logs app` ([architettura.md](architettura.md) ARC-09).
|
||||
indirizzo secondo il **solo server attivo**: mai partita (riga `building` da
|
||||
oltre 120s) o scomparsa dal mempool (riga `pending` da oltre 6 ore dal primo
|
||||
invio). Non è una prova: un server diverso da quello che ha ricevuto l'invio
|
||||
può non conoscerla, e una versione precedente di una transazione accelerata
|
||||
con RBF non viene cercata (B-77).
|
||||
può non conoscerla (si cercano tutte le versioni di una transazione
|
||||
accelerata con RBF, ma solo sul server attivo).
|
||||
**Effetto e cosa fare.** Il sistema ha già annullato tutto: UTXO liberati e
|
||||
saldo ricalcolato; per una scommessa il partecipante è rimosso (l'utente può
|
||||
riscommettere se il round è ancora aperto); per un prelievo la richiesta è
|
||||
|
||||
+3
-3
@@ -225,7 +225,7 @@ cifrava di nuovo il file sbagliato (vedi [operativita.md](operativita.md)).
|
||||
| B-74 | Header accettati senza controllo della difficoltà minima; un salto di più blocchi non viene collegato; una riorganizzazione di un blocco chiude la sessione | Critica | **aperto** | — | ELC |
|
||||
| B-75 | Una risposta Electrum oltre 64 KiB chiude la connessione, e la cronologia dell'indirizzo del montepremi la supera dopo ~800 transazioni | Critica | *da registrare* | `test_electrum_client.py`, `test_deposits.py`, `test_psbt_builder.py` | ELC, TX |
|
||||
| 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 | **aperto** | — | TX |
|
||||
| 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 | **aperto** | — | 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-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 |
|
||||
@@ -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.
|
||||
come limite accettato; dalla revisione del 2026-09-28: B-88, B-75, B-97, B-80, B-84, B-76, B-77.
|
||||
- **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 (7 critici,
|
||||
B-73 … B-98 dalla revisione del 2026-09-28 tranne i corretti (6 critici,
|
||||
7 alti, 6 medi), da implementare nell'ordine di
|
||||
[piano-correzioni.md](piano-correzioni.md).
|
||||
- **Prossimo numero libero:** `B-99`.
|
||||
@@ -69,11 +69,8 @@ stateDiagram-v2
|
||||
la versione trovata in catena non è quella corrente, `current_txid` e
|
||||
`raw_tx_hex` diventano quelli della versione trovata e il txid viene
|
||||
propagato ovunque sia registrato, come dopo un RBF ([TX-12]).
|
||||
*Codice:* `app/tx/confirmation.py:poll_once`, `app/tx/reconcile.py:reconcile_once`, `app/tx/broadcast.py:_retarget_txid_references` ·
|
||||
*Test:* —
|
||||
*Stato:* da implementare (B-77) · *Oggi:* si conserva solo il txid corrente
|
||||
(più il precedente in `replaced_by_txid`, che nessuno legge); se conferma
|
||||
una versione precedente a un RBF non viene riconosciuta.
|
||||
*Codice:* `app/tx/versions.py`, `app/tx/confirmation.py:poll_once`, `app/tx/reconcile.py:reconcile_once`, `app/tx/broadcast.py:adopt_version`, `app/tx/broadcast.py:_retarget_txid_references` ·
|
||||
*Test:* `tests/unit/test_tx_versions.py`, `tests/unit/test_migrations.py`
|
||||
- **[TX-28]** Ogni transazione del database che sposta denaro — la scrittura
|
||||
di `building` ([TX-01]), la promozione dopo l'invio, l'annullamento dopo un
|
||||
rifiuto, le due scritture dell'RBF ([TX-12]), la promozione e l'abbandono
|
||||
@@ -147,9 +144,7 @@ monete:
|
||||
questi invii viene solo registrato nei log, non cambia lo stato della riga
|
||||
e non ritarda la risposta all'utente.
|
||||
*Codice:* `app/electrum/listener.py:ElectrumListener.fan_out_transaction`, `app/tx/outcome.py:broadcast_with_verdict`, `app/bets/service.py:place_bet`, `app/withdrawals/service.py:request_withdrawal`, `app/rounds/scheduler.py:RoundScheduler._trigger_payout`, `app/tx/broadcast.py:bump_fee` ·
|
||||
*Test:* `tests/unit/test_broadcast_outcome.py`
|
||||
*Stato:* da implementare (B-77), per la sostituzione RBF · *Oggi:* la
|
||||
sostituzione di `bump_fee` viene inviata solo al server attivo.
|
||||
*Test:* `tests/unit/test_broadcast_outcome.py`, `tests/unit/test_tx_versions.py`
|
||||
- **[TX-05]** Se l'invio di un pagamento non è accettato — rifiuto esplicito
|
||||
o esito incerto — la riga resta `building` e si scrive `payout_failed`
|
||||
(`broadcast rejected`): è il riconciliatore a decidere, interrogando la
|
||||
@@ -172,10 +167,9 @@ monete:
|
||||
per indirizzo a ogni giro; un indirizzo che non si riesce a leggere non
|
||||
blocca gli altri.
|
||||
*Codice:* `app/tx/confirmation.py:poll_once`, `app/tx/pending_address.py:own_address_for` ·
|
||||
*Test:* `tests/unit/test_confirmation.py`, `tests/unit/test_round_pool_address.py`
|
||||
*Stato:* da implementare (B-77) · *Oggi:* cerca solo `current_txid`,
|
||||
nella cronologia dell'indirizzo del montepremi del round per i pagamenti; un'altezza maggiore di 0 riportata dal solo server
|
||||
attivo basta a confermare ([TX-22]).
|
||||
*Test:* `tests/unit/test_confirmation.py`, `tests/unit/test_round_pool_address.py`, `tests/unit/test_tx_versions.py`
|
||||
*Stato:* da implementare (B-83) · *Oggi:* un'altezza maggiore di 0
|
||||
riportata dal solo server attivo basta a confermare ([TX-22]).
|
||||
- **[TX-22]** **Conferma verificata.** Un candidato ([TX-06]) è confermato
|
||||
solo quando il quorum dei server
|
||||
([connessione-electrum.md](connessione-electrum.md) (B-84)) restituisce per
|
||||
@@ -277,11 +271,7 @@ sostituibilità fin dal primo invio.
|
||||
round (`payout_txid`). Se la sostituzione è rifiutata esplicitamente la
|
||||
riga non cambia ([TX-24]).
|
||||
*Codice:* `app/tx/broadcast.py:bump_fee`, `app/tx/broadcast.py:_retarget_txid_references` ·
|
||||
*Test:* `tests/unit/test_broadcast.py`
|
||||
*Stato:* da implementare (B-77) · *Oggi:* la sostituzione viene inviata
|
||||
senza essere registrata, e la riga viene aggiornata solo dopo l'invio, senza
|
||||
condizioni: un crash o un `database is locked` in mezzo lascia in rete una
|
||||
transazione che il database non conosce.
|
||||
*Test:* `tests/unit/test_broadcast.py`, `tests/unit/test_tx_versions.py`
|
||||
- **[TX-13]** L'RBF non tocca una riga che è cambiata sotto di lui. Prima di
|
||||
costruire la sostituzione, se la riga non esiste più o non è più
|
||||
`pending` (ad esempio è appena stata confermata), non si fa nulla. Dopo
|
||||
@@ -291,11 +281,7 @@ sostituibilità fin dal primo invio.
|
||||
candidata resta registrata — la conferma e la riconciliazione la cercano
|
||||
comunque ([TX-19]) — e il conflitto viene scritto nei log.
|
||||
*Codice:* `app/tx/broadcast.py:bump_fee` ·
|
||||
*Test:* `tests/unit/test_broadcast.py`
|
||||
*Stato:* da implementare (B-77) · *Oggi:* il controllo avviene solo prima
|
||||
della costruzione; dopo l'invio la riga viene aggiornata senza ricontrollare
|
||||
né lo stato né `current_txid`, anche se nel frattempo è stata confermata o
|
||||
abbandonata.
|
||||
*Test:* `tests/unit/test_broadcast.py`, `tests/unit/test_tx_versions.py`
|
||||
- **[TX-24]** Un'accelerazione impossibile ([TX-11], o la riga già a
|
||||
`MAX_FEE_RATE_SAT_VB`) o una sostituzione rifiutata esplicitamente dalla
|
||||
rete scrivono un evento `rbf_bump_failed` ([audit-log.md](audit-log.md)
|
||||
@@ -342,9 +328,9 @@ riserva di [TX-11] serve proprio a dargli un'uscita da ridurre.
|
||||
([TX-17]). Un **errore di rete** non porta a nulla: la riga resta com'è
|
||||
fino al giro successivo.
|
||||
*Codice:* `app/tx/reconcile.py:reconcile_once`, `app/electrum/listener.py:ElectrumListener._corroborate_majority` ·
|
||||
*Test:* `tests/unit/test_reconcile.py`
|
||||
*Test:* `tests/unit/test_reconcile.py`, `tests/unit/test_tx_versions.py`
|
||||
*Stato:* da implementare (B-78) · *Oggi:* si interroga solo il server
|
||||
attivo e solo per `current_txid`, e un txid assente dalla sua cronologia è
|
||||
attivo, e un txid assente dalla sua cronologia è
|
||||
trattato come "sconosciuto alla rete": dopo un riavvio su un server diverso
|
||||
una riga `building` viene abbandonata dopo 120s anche se la transazione è
|
||||
nella mempool degli altri nodi (che la tengono fino a 336 ore con le
|
||||
|
||||
Reference in new issue
Block a user