chaintopology: fix RBF loop that never stops after replacement tx confirms
What changed, and why it matters
This fix resolves a bug where Core Lightning would keep trying to replace a transaction with a higher-fee version forever, even after the replacement had already been confirmed on the Bitcoin blockchain. The loop happened because the code checked the old transaction ID instead of the current one. This wasted resources, created unnecessary transactions, and could bloat the wallet or leak funds through repeated fees.
Apply the patch. Monitor affected nodes for unusually high numbers of RBF replacement transactions in on-chain channel-close flows, and consider wallet cleanup if the bug has already triggered.
Security signals we found
CWE-835: Infinite Loop
Resource exhaustion via repeated on-chain transaction creation
Potential fee loss / wallet bloat from perpetual RBF replacements
Logic error using stale key instead of current transaction state
Evidence from the diff
In chaintopology.c, rebroadcast_txs() iterates outgoing transactions and skips any already confirmed by looking up wallet_transaction_height() using otx->txid. However, after a prior refresh() call replaces otx->tx with a higher-fee RBF version, otx->txid (the hash-map key) remains the original transaction ID. Since the original txid was never mined, wallet_transaction_height() always returns 0, so the RBF rebroadcast loop runs on every new block indefinitely. The patch computes cur_txid from the current otx->tx before the confirmation guard, leaving the map key untouched to avoid hash-map corruption.
Changed components
lightningd/chaintopology.crebroadcast_txs()outgoing_tx_mapRBF fee-bumping pathInspect captured patch +7 / −2
diff --git a/lightningd/chaintopology.c b/lightningd/chaintopology.c
index e59b9c63..b77b51df 100644
--- a/lightningd/chaintopology.c
+++ b/lightningd/chaintopology.c
@@ -166,8 +166,13 @@ static void rebroadcast_txs(struct chain_topology *topo)
for (otx = outgoing_tx_map_first(topo->outgoing_txs, &it); otx;
otx = outgoing_tx_map_next(topo->outgoing_txs, &it)) {
struct tx_rebroadcast *txrb;
- /* Already sent? */
- if (wallet_transaction_height(topo->ld->wallet, &otx->txid))
+ struct bitcoin_txid cur_txid;
+
+ /* Already confirmed? Use the txid of the current tx, not the
+ * original otx->txid: refresh() may have replaced otx->tx with
+ * a higher-fee version whose txid differs from the map key. */
+ bitcoin_txid(otx->tx, &cur_txid);
+ if (wallet_transaction_height(topo->ld->wallet, &cur_txid))
continue;
/* Don't send ones which aren't ready yet. Note that if the
Why this scored 59/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.