Never roll back an ambiguous broadcast as if it had been refused (B-76, TX-04, TX-05, TX-20, TX-21, BET-16, BET-17, BET-23, WDR-11, WDR-12, WDR-16, API-13)
place_bet and request_withdrawal released everything on any exception from the broadcast, including the 15s timeout and a connection dropped after the write. The tx may have propagated: a bet's sats landed in the pool with no participant, a withdrawal left while its row said failed and its coins were spendable again. - client: ElectrumRpcError (subclass of ElectrumError) is raised only for a reply carrying a JSON-RPC `error`; timeouts, drops and write errors stay plain ElectrumError. - tx/outcome.py:broadcast_with_verdict sorts a broadcast into accepted / refused / uncertain. A refusal is verified first: ElectrumListener.transaction_known asks blockchain.transaction.get of the active server and every other one (10s each) — any server returning the tx means accepted; the active server plus MIN_QUORUM_RESPONSES others all denying it means refused (ALLOW_NO_QUORUM keeps the reduced rule); too few answers is uncertain. "already known" wording is only a shortcut that skips the check. With no network to ask, a refusal is uncertain. - place_bet / request_withdrawal / _trigger_payout use it: only a verified refusal releases; uncertain leaves the rows building for the reconciler, publishes SSE and POST /bets and POST /withdrawals answer 202 with status building; "already known" promotes a payout (payout_sent) like any accepted broadcast. - An accepted transaction is fanned out to every other server (ElectrumListener.fan_out_transaction, best effort, off the caller's path). The RBF replacement in bump_fee is left for B-77, which rewrites that function. Tests changed because the spec changed: the existing "refused broadcast" tests raised a bare RuntimeError (now an uncertain outcome) and are made explicit refusals (rpc_error) with a network that denies the tx; direct route calls pass a Response; FakePayoutListener gains the network surface. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
13cb66cc06
commit
2b6ddb019b
27 files changed
+965
-150
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-76, B-77)
|
||||
- **Stato:** Accettata — implementazione da completare (B-77)
|
||||
- **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*.
|
||||
|
||||
@@ -19,7 +19,6 @@ riconciliatore e risponde **202** ([ADR-51](ADR-51-dubbio-liberano-mai-fondi.md)
|
||||
[transazioni-e-riconciliazione.md](../transazioni-e-riconciliazione.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_scheduler.py::test_trigger_payout_persists_before_broadcasting`, `tests/unit/test_reconcile.py`
|
||||
*Stato:* da implementare (B-76, B-77) · *Oggi:* qualunque eccezione
|
||||
dell'invio, timeout compreso, annulla tutto con 502; `bump_fee` invia la
|
||||
sostituzione prima di salvarla.
|
||||
*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-76, B-77, B-78)
|
||||
- **Stato:** Accettata — implementazione da completare (B-77, 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*.
|
||||
|
||||
@@ -36,8 +36,8 @@ pausa la lotteria ([ADR-28](ADR-28-pausa-manutenzione-ferma-round-successivo.md)
|
||||
congelato e visibile, mai duplicato.
|
||||
|
||||
*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:* —
|
||||
*Stato:* da implementare (B-76, B-77, B-78) · *Oggi:* l'abbandono avviene
|
||||
*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.
|
||||
+3
-10
@@ -220,10 +220,7 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
"già noto / già nel mempool" vale come successo (`201`).
|
||||
*Codice:* `app/api/routes/bets.py:create_bet`,
|
||||
`app/api/routes/withdrawals.py:create_withdrawal` ·
|
||||
*Test:* `tests/unit/test_bets.py`
|
||||
*Stato:* da implementare (B-76) · *Oggi:* qualunque eccezione del broadcast,
|
||||
compresi il timeout di 15 s e la caduta della connessione, annulla tutto e
|
||||
risponde `502 broadcast_failed`.
|
||||
*Test:* `tests/unit/test_bets.py`, `tests/unit/test_broadcast_outcome.py`
|
||||
|
||||
- **[API-14]** Qualunque eccezione non gestita risponde `500` con lo stesso
|
||||
formato strutturato, codice `internal_error`, messaggio
|
||||
@@ -471,9 +468,7 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
*Codice:* `app/api/routes/bets.py:create_bet`,
|
||||
`app/bets/service.py:place_bet` ·
|
||||
*Test:* `tests/unit/test_bets.py`
|
||||
*Stato:* da implementare (B-76, B-95, B-96) · *Oggi:* ogni esito diverso
|
||||
dal successo è `502 broadcast_failed` con tutto annullato; la trasmissione
|
||||
va solo al server attivo; un costo coperto solo da resto non confermato
|
||||
*Stato:* da implementare (B-95, B-96) · *Oggi:* un costo coperto solo da resto non confermato
|
||||
risponde `insufficient_balance`; non c'è tetto al montepremi; il costo è
|
||||
quello configurato al momento della scommessa, non quello del round.
|
||||
|
||||
@@ -521,9 +516,7 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
*Codice:* `app/api/routes/withdrawals.py:create_withdrawal`,
|
||||
`app/withdrawals/service.py:request_withdrawal` ·
|
||||
*Test:* `tests/unit/test_withdrawals.py`
|
||||
*Stato:* da implementare (B-76, B-96) · *Oggi:* ogni esito diverso
|
||||
dal successo è `502 broadcast_failed` con il prelievo `failed`; la
|
||||
trasmissione va solo al server attivo; l'importo ridotto di un prelievo
|
||||
*Stato:* da implementare (B-96) · *Oggi:* l'importo ridotto di un prelievo
|
||||
dell'intero saldo non è riconfrontato con il minimo.
|
||||
|
||||
### Round
|
||||
|
||||
+2
-6
@@ -352,9 +352,7 @@ flowchart LR
|
||||
200 caratteri. `user_id` sì, `round_id` **no** (a differenza di
|
||||
`bet_placed`). Stessa transazione del ripristino.
|
||||
*Codice:* `app/bets/service.py:_release_failed_bet` ·
|
||||
*Test:* `tests/unit/test_bets.py`
|
||||
*Stato:* da implementare (B-76) · *Oggi:* scritto per qualunque errore
|
||||
d'invio, compresi timeout e disconnessioni.
|
||||
*Test:* `tests/unit/test_bets.py`, `tests/unit/test_broadcast_outcome.py`
|
||||
|
||||
- **[AUD-21]** `withdrawal_sent` — la rete ha accettato la transazione di un
|
||||
prelievo. Payload `{"txid", "amount_sent_sats", "external_address"}`:
|
||||
@@ -373,9 +371,7 @@ flowchart LR
|
||||
(`reason` troncato a 200 caratteri). `user_id` sì, `round_id` no. Stessa
|
||||
transazione del ripristino.
|
||||
*Codice:* `app/withdrawals/service.py:_release_failed_withdrawal` ·
|
||||
*Test:* nessun test verifica la riga di audit.
|
||||
*Stato:* da implementare (B-76) · *Oggi:* scritto per qualunque errore
|
||||
d'invio, compresi timeout e disconnessioni.
|
||||
*Test:* `tests/unit/test_broadcast_outcome.py`
|
||||
|
||||
### Round
|
||||
|
||||
|
||||
@@ -349,7 +349,8 @@ 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 al solo server attivo.
|
||||
B-83); oggi l'invio va anche a tutti gli altri server, salvo la sostituzione
|
||||
RBF di `bump_fee` (B-77).
|
||||
|
||||
## Riconnessione
|
||||
|
||||
|
||||
+8
-11
@@ -392,10 +392,9 @@ del database sono tutti uguali, perché arrivano tutti dallo stesso percorso
|
||||
*Codice:* le funzioni in tabella ·
|
||||
*Test:* `tests/unit/test_deposits.py`, `tests/unit/test_bets.py`,
|
||||
`tests/unit/test_withdrawals.py`, `tests/unit/test_reconcile.py`
|
||||
*Stato:* da implementare (B-76, B-78, B-83) · *Oggi:* il rilascio dopo il
|
||||
broadcast avviene su qualunque eccezione (anche un timeout), l'abbandono
|
||||
sul silenzio del solo server attivo, il reintegro sulla parola del solo
|
||||
server attivo.
|
||||
*Stato:* da implementare (B-78, B-83) · *Oggi:* l'abbandono avviene sul
|
||||
silenzio del solo server attivo, il reintegro sulla parola del solo server
|
||||
attivo.
|
||||
|
||||
- **[DAT-13]** Il riconciliatore libera un input solo se il suo `spent_txid` è
|
||||
uno dei txid della riga che sta abbandonando (`current_txid` o una sua
|
||||
@@ -975,11 +974,9 @@ Le funzioni citate sono `app/bets/service.py:place_bet`,
|
||||
*Test:* `tests/unit/test_confirmation.py`, `tests/unit/test_reconcile.py`,
|
||||
`tests/unit/test_scheduler.py`, `tests/unit/test_bets.py`,
|
||||
`tests/unit/test_withdrawals.py`
|
||||
*Stato:* da implementare (B-76, B-78) · *Oggi:* qualunque eccezione del
|
||||
broadcast di una scommessa o di un prelievo è trattata come un rifiuto;
|
||||
l'abbandono avviene quando la cronologia del solo server attivo non
|
||||
contiene `current_txid`; il poller guarda solo le righe `pending` e una
|
||||
riga `failed` non viene più guardata.
|
||||
*Stato:* da implementare (B-78) · *Oggi:* l'abbandono avviene quando la
|
||||
cronologia del solo server attivo non contiene `current_txid`; il poller
|
||||
guarda solo le righe `pending` e una riga `failed` non viene più guardata.
|
||||
|
||||
- **[DAT-34]** Dopo la creazione, `current_txid`, `raw_tx_hex`,
|
||||
`fee_rate_sat_vb`, `replaced_by_txid`, `attempt_count` e `last_broadcast_at`
|
||||
@@ -1157,8 +1154,8 @@ dall'utente.
|
||||
([DAT-72]), il prelievo resta `failed` finché l'operatore non lo riallinea.
|
||||
*Codice:* le funzioni in tabella ·
|
||||
*Test:* `tests/unit/test_withdrawals.py`, `tests/unit/test_reconcile.py`
|
||||
*Stato:* da implementare (B-76, B-78) · *Oggi:* qualunque eccezione del
|
||||
broadcast porta a `failed`, e l'abbandono non richiede una prova.
|
||||
*Stato:* da implementare (B-78) · *Oggi:* l'abbandono non richiede una
|
||||
prova.
|
||||
|
||||
- **[DAT-40]** `amount_requested_sats` è ciò che l'utente ha chiesto;
|
||||
`amount_sent_sats` è l'output effettivo verso il destinatario: l'importo
|
||||
|
||||
@@ -203,11 +203,7 @@ flowchart TB
|
||||
registro di audit, notifica SSE; la risposta è `broadcast_failed`
|
||||
(HTTP 502).
|
||||
*Codice:* `app/bets/service.py:_release_failed_bet`, `app/api/routes/bets.py:create_bet` ·
|
||||
*Test:* `tests/unit/test_bets.py`
|
||||
*Stato:* da implementare (B-76) · *Oggi:* tutto viene annullato per
|
||||
qualunque errore dell'invio, anche un timeout o una connessione caduta
|
||||
dopo la scrittura, quando la transazione può essere già in rete: i suoi
|
||||
sat arrivano al montepremi senza partecipante.
|
||||
*Test:* `tests/unit/test_bets.py`, `tests/unit/test_broadcast_outcome.py`
|
||||
- **[BET-23]** Se l'invio ha un **esito incerto**
|
||||
([TX-20](transazioni-e-riconciliazione.md#invio-rifiutato)), partecipante e
|
||||
transazione in corso restano `building` con le monete ancora segnate come
|
||||
@@ -216,9 +212,7 @@ flowchart TB
|
||||
limite di [BET-06] e tiene il round in `closing` finché il riconciliatore
|
||||
non lo promuove o lo abbandona.
|
||||
*Codice:* `app/bets/service.py:place_bet`, `app/api/routes/bets.py:create_bet` ·
|
||||
*Test:* —
|
||||
*Stato:* da implementare (B-76) · *Oggi:* un esito incerto è trattato come
|
||||
un rifiuto ([BET-16]).
|
||||
*Test:* `tests/unit/test_broadcast_outcome.py`
|
||||
- **[BET-17]** Se l'invio è accettato — anche con un rifiuto "già nota"
|
||||
([TX-04](transazioni-e-riconciliazione.md#invio-rifiutato)) — il
|
||||
partecipante passa a `broadcast`, la transazione in corso a `pending`, si
|
||||
@@ -229,9 +223,7 @@ flowchart TB
|
||||
scommessa promossa dal riconciliatore scrive `bet_placed` in quel momento
|
||||
([TX-16](transazioni-e-riconciliazione.md#riconciliazione)).
|
||||
*Codice:* `app/bets/service.py:place_bet`, `app/api/routes/bets.py:create_bet` ·
|
||||
*Test:* `tests/unit/test_bets.py`
|
||||
*Stato:* da implementare (B-76) · *Oggi:* un "già nota" è trattato come un
|
||||
rifiuto e la transazione va solo al server attivo.
|
||||
*Test:* `tests/unit/test_bets.py`, `tests/unit/test_broadcast_outcome.py`
|
||||
- **[BET-18]** Alla conferma il partecipante passa a `confirmed`
|
||||
([TX-07](transazioni-e-riconciliazione.md#conferma)). Solo le scommesse
|
||||
**confermate** partecipano all'estrazione; il round, allo scadere, aspetta
|
||||
@@ -324,12 +316,7 @@ solo se la rete rifiuta esplicitamente, RBF se non conferma in tempo.
|
||||
`failure_reason`); si scrive `withdrawal_broadcast_failed`, si invia una
|
||||
notifica SSE e la risposta è `broadcast_failed` (HTTP 502).
|
||||
*Codice:* `app/withdrawals/service.py:_release_failed_withdrawal`, `app/api/routes/withdrawals.py:create_withdrawal` ·
|
||||
*Test:* `tests/unit/test_withdrawals.py` (nessun test verifica l'evento
|
||||
`withdrawal_broadcast_failed`)
|
||||
*Stato:* da implementare (B-76) · *Oggi:* tutto viene annullato per
|
||||
qualunque errore dell'invio, anche un timeout o una connessione caduta
|
||||
dopo la scrittura: la transazione può essere partita mentre il prelievo
|
||||
risulta `failed` e le monete di nuovo spendibili.
|
||||
*Test:* `tests/unit/test_withdrawals.py`, `tests/unit/test_broadcast_outcome.py`
|
||||
- **[WDR-16]** Se l'invio ha un **esito incerto**
|
||||
([TX-20](transazioni-e-riconciliazione.md#invio-rifiutato)), prelievo e
|
||||
transazione in corso restano `building` con le monete ancora segnate come
|
||||
@@ -337,9 +324,7 @@ solo se la rete rifiuta esplicitamente, RBF se non conferma in tempo.
|
||||
`amount_sent_sats` e `status` = `building`; sarà il riconciliatore a
|
||||
promuoverlo o abbandonarlo.
|
||||
*Codice:* `app/withdrawals/service.py:request_withdrawal`, `app/api/routes/withdrawals.py:create_withdrawal` ·
|
||||
*Test:* —
|
||||
*Stato:* da implementare (B-76) · *Oggi:* un esito incerto è trattato come
|
||||
un rifiuto ([WDR-11]).
|
||||
*Test:* `tests/unit/test_broadcast_outcome.py`
|
||||
- **[WDR-12]** Se l'invio è accettato — anche con un rifiuto "già nota"
|
||||
([TX-04](transazioni-e-riconciliazione.md#invio-rifiutato)) — il prelievo
|
||||
passa a `broadcast`, la transazione in corso a `pending`, si scrive
|
||||
@@ -350,9 +335,7 @@ solo se la rete rifiuta esplicitamente, RBF se non conferma in tempo.
|
||||
`status`. Alla conferma il prelievo passa a `confirmed`
|
||||
([TX-07](transazioni-e-riconciliazione.md#conferma)).
|
||||
*Codice:* `app/withdrawals/service.py:request_withdrawal`, `app/withdrawals/confirmation.py:_on_withdrawal_confirmed` ·
|
||||
*Test:* `tests/unit/test_withdrawals.py`
|
||||
*Stato:* da implementare (B-76) · *Oggi:* un "già nota" è trattato come un
|
||||
rifiuto e la transazione va solo al server attivo.
|
||||
*Test:* `tests/unit/test_withdrawals.py`, `tests/unit/test_broadcast_outcome.py`
|
||||
|
||||
## Scommessa e prelievo insieme
|
||||
|
||||
|
||||
+3
-3
@@ -224,7 +224,7 @@ cifrava di nuovo il file sbagliato (vedi [operativita.md](operativita.md)).
|
||||
| B-73 | L'estrazione dipende solo dall'hash del blocco: un minatore con biglietti o il server Electrum attivo possono pilotarla; ordine dei partecipanti non verificabile | Critica | **aperto** | — | RND, ELC |
|
||||
| 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 | **aperto** | — | TX, BET, WDR |
|
||||
| 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-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 |
|
||||
@@ -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.
|
||||
come limite accettato; dalla revisione del 2026-09-28: B-88, B-75, B-97, B-80, B-84, B-76.
|
||||
- **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 (8 critici,
|
||||
B-73 … B-98 dalla revisione del 2026-09-28 tranne i corretti (7 critici,
|
||||
7 alti, 6 medi), da implementare nell'ordine di
|
||||
[piano-correzioni.md](piano-correzioni.md).
|
||||
- **Prossimo numero libero:** `B-99`.
|
||||
@@ -127,12 +127,8 @@ monete:
|
||||
`transaction already in block chain`) resta solo una scorciatoia che salta
|
||||
la verifica: un messaggio non riconosciuto non può più far liberare le
|
||||
monete di una transazione partita, al massimo costa una verifica in più.
|
||||
*Codice:* `app/bets/service.py:_release_failed_bet`, `app/withdrawals/service.py:_release_failed_withdrawal`, `app/electrum/client.py:ElectrumClient.broadcast`, `app/electrum/client.py:ElectrumClient.get_transaction` ·
|
||||
*Test:* `tests/unit/test_bets.py`, `tests/unit/test_withdrawals.py`
|
||||
*Stato:* da implementare (B-76) · *Oggi:* qualunque eccezione dell'invio,
|
||||
compresi il timeout di 15 secondi, la connessione caduta dopo la scrittura
|
||||
e il "già nota", annulla tutto e risponde 502, anche se la transazione può
|
||||
essere già in rete.
|
||||
*Codice:* `app/tx/outcome.py:broadcast_with_verdict`, `app/electrum/client.py:ElectrumRpcError`, `app/electrum/listener.py:ElectrumListener.transaction_known`, `app/bets/service.py:_release_failed_bet`, `app/withdrawals/service.py:_release_failed_withdrawal` ·
|
||||
*Test:* `tests/unit/test_broadcast_outcome.py`, `tests/unit/test_electrum_client.py`, `tests/unit/test_bets.py`, `tests/unit/test_withdrawals.py`
|
||||
- **[TX-20]** **Invio incerto.** Se l'invio di una scommessa o di un prelievo
|
||||
finisce senza una risposta del server (timeout, connessione caduta, errore
|
||||
di scrittura), o se il commit della promozione successiva a un invio
|
||||
@@ -142,21 +138,18 @@ monete:
|
||||
[TX-17]). L'utente riceve HTTP **202** con l'oggetto nello stato
|
||||
`building` ([BET-23](scommesse-e-prelievi.md#cosa-succede),
|
||||
[WDR-16](scommesse-e-prelievi.md#prelievo)) e si invia una notifica SSE.
|
||||
*Codice:* `app/bets/service.py:place_bet`, `app/withdrawals/service.py:request_withdrawal` ·
|
||||
*Test:* —
|
||||
*Stato:* da implementare (B-76) · *Oggi:* l'invio incerto è trattato come
|
||||
un rifiuto ([TX-04]); un commit fallito dopo un invio accettato solleva un
|
||||
errore 500 lasciando le righe `building`.
|
||||
*Codice:* `app/bets/service.py:place_bet`, `app/withdrawals/service.py:request_withdrawal`, `app/api/routes/bets.py:create_bet`, `app/api/routes/withdrawals.py:create_withdrawal` ·
|
||||
*Test:* `tests/unit/test_broadcast_outcome.py`, `tests/unit/test_sqlite_transactions.py`
|
||||
- **[TX-21]** **Invio a tutti i server.** Dopo che il server attivo ha
|
||||
accettato una transazione ([TX-04]) — scommessa, prelievo, pagamento o
|
||||
sostituzione RBF — la stessa transazione viene inviata anche a ogni altro
|
||||
server configurato. È un tentativo in più, non una condizione: un errore di
|
||||
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`, `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:* —
|
||||
*Stato:* da implementare (B-76) · *Oggi:* ogni transazione viene inviata
|
||||
solo al server attivo.
|
||||
*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.
|
||||
- **[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
|
||||
@@ -164,10 +157,7 @@ monete:
|
||||
"già nota" ([TX-04]) vale come invio accettato: la riga passa a `pending` e
|
||||
si scrive `payout_sent`.
|
||||
*Codice:* `app/rounds/scheduler.py:RoundScheduler._trigger_payout` ·
|
||||
*Test:* `tests/unit/test_scheduler.py`
|
||||
*Stato:* da implementare (B-76) · *Oggi:* anche il "già nota" lascia la
|
||||
riga `building` con `payout_failed`, finché il riconciliatore non la
|
||||
promuove.
|
||||
*Test:* `tests/unit/test_scheduler.py`, `tests/unit/test_broadcast_outcome.py`
|
||||
|
||||
## Conferma
|
||||
|
||||
|
||||
Reference in new issue
Block a user