fix(pool): keep the node's rejection reason when a broadcast fails - #8
Open
criptoworld8484 wants to merge 1 commit into
Open
fix(pool): keep the node's rejection reason when a broadcast fails#8criptoworld8484 wants to merge 1 commit into
criptoworld8484 wants to merge 1 commit into
Conversation
A failed broadcast was recorded as the bare context line ("Failed to broadcast
transaction"): every backend wraps the node's reason in a `.context()`, so
`e.to_string()` threw away the only part that says *why*.
Two consequences, one cosmetic and one not:
- The dashboard showed a message that explains nothing. Debugging a real
incident on the node, "Failed to broadcast transaction" was indistinguishable
from "the Bitcoin Core fallback is broken" — the tx was in fact a double spend
(bad-txns-inputs-missingorspent), which no backend would ever accept.
- is_retriable_broadcast_error() matches on "non-final", "locktime" and
"too-long-mempool-chain", all of which live in the discarded source. It was
therefore blind: a tx the node would accept one block later got marked
permanently failed instead of being retried.
Render with `{:#}` so the whole error chain reaches both the retry decision and
the user.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Qué pasó
Probando el fallback de la 0.3.18 en el nodo (Umbrel testnet4): se para Fulcrum, una tx programada por fecha/hora vence… y el dashboard dice "Failed to broadcast transaction". Parecía que el fallback a Bitcoin Core no funcionaba.
No era eso. Los logs del nodo muestran que el fallback hizo exactamente su trabajo:
source=BitcoinCore indexer_up=false core_up=true, la altura siguió avanzando (144078 → 144085) con Fulcrum parado, y el scheduler marcó la tx vencida y la intentó difundir a su hora.Lo que fallaba era la tx: gasta los dos mismos UTXOs que otra tx del pool que ya se había confirmado doce minutos antes. Es un doble gasto, y la red lo rechaza siempre —
bad-txns-inputs-missingorspent, tanto por Core como por Fulcrum cuando volvió.El bug real
El motivo del rechazo no llegaba a ninguna parte. Todos los backends envuelven el error del nodo en un
.context(), así quee.to_string()se queda solo con nuestra línea de contexto y tira la causa. Eso tiene dos consecuencias:is_retriable_broadcast_error()decide si reintentar buscandonon-final,locktimeotoo-long-mempool-chainen el mensaje… que son palabras que viven en la causa descartada. Estaba ciega: una tx rechazada por non-final, que el nodo aceptaría un bloque más tarde, se marcaba comofaileddefinitiva en vez de reintentarse.El arreglo
Renderizar el error con
{:#}(cadena completa) en vez de{}, de modo que la causa llegue tanto a la decisión de reintento como alerror_messageque ve el usuario.Dos tests nuevos fijan el contrato: con la cadena completa un
non-finalse reconoce como reintentable, y unbad-txns-inputs-missingorspentno. 43 tests pasan.🤖 Generated with Claude Code