lightningd: don't try to re-xmit funding tx for already-confirmed channels.
What changed, and why it matters
This commit fixes a harmless but noisy bug in Core Lightning. On every restart, the node was trying to re-broadcast the funding transaction for channels that were already confirmed long ago. Bitcoin rejected these with a routine 'already known' error, which just cluttered the logs. The fix changes the startup check so re-broadcast only happens for channels that are actually still waiting for their first confirmation.
No urgent action required. This is a log-noise/operational fix. Operators can upgrade at their normal cadence; the bug does not expose funds or allow remote exploitation.
Security signals we found
Spurious re-broadcast of funding transactions on node restart
Reliance on a field (channel->depth) that is uninitialized at the point of use
Routine bitcoind error -27 ('Transaction outputs already in utxo set') surfaced as UNUSUAL log
No loss of funds or consensus failure; issue is operational/log noise
Evidence from the diff
resend_opening_transactions() runs at startup before begin_topology(), so channel->depth is still the DB-load default of 0 for every channel. The previous guard if (channel->depth != 0) continue; therefore never fired, causing sendrawtransaction to be issued for every committed channel on every restart. The patch removes the depth guard and instead checks the channel state, only retransmitting for CHANNELD_AWAITING_LOCKIN, DUALOPEND_AWAITING_LOCKIN, and CHANNELD_AWAITING_SPLICE. The test is updated to assert the spurious log no longer appears.
Changed components
lightningd/peer_control.cresend_opening_transactions()tests/test_opening.py::test_no_retransmit_confirmed_fundingInspect captured patch +8 / −4
diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c
index 4cbc4a00..8ec1cb79 100644
--- a/lightningd/peer_control.c
+++ b/lightningd/peer_control.c
@@ -565,12 +565,16 @@ void resend_opening_transactions(struct lightningd *ld)
peer = peer_node_id_map_next(ld->peers, &it)) {
list_for_each(&peer->channels, channel, list) {
struct wally_tx *wtx;
- if (channel_state_uncommitted(channel->state))
+ /* Only states where the funding/splice tx might
+ * still be unconfirmed. channel->depth can't be
+ * used here: it's reset to 0 on DB load and only
+ * repopulated once topology starts. */
+ if (channel->state != CHANNELD_AWAITING_LOCKIN
+ && channel->state != DUALOPEND_AWAITING_LOCKIN
+ && channel->state != CHANNELD_AWAITING_SPLICE)
continue;
if (!channel->funding_psbt || channel->withheld)
continue;
- if (channel->depth != 0)
- continue;
wtx = psbt_final_tx(tmpctx, channel->funding_psbt);
if (!wtx)
continue;
diff --git a/tests/test_opening.py b/tests/test_opening.py
index 2c21a78e..82f2e6ba 100644
--- a/tests/test_opening.py
+++ b/tests/test_opening.py
@@ -3087,5 +3087,5 @@ def test_no_retransmit_confirmed_funding(node_factory):
l1.restart()
# Should not have attempted (and failed) to re-broadcast the funding tx.
- assert l1.daemon.is_in_log('Failed to re-transmit funding tx')
+ assert not l1.daemon.is_in_log('Failed to re-transmit funding tx')
assert not l1.daemon.is_in_log('Successfully rexmitted funding tx')
Why this scored 25/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.