From be6c81a28344c6a6f9475bb411d217d638fc68d1 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Wed, 25 Mar 2026 13:46:59 +0100 Subject: [PATCH 1/3] fix: restore missing returns in send_channel_announce_sigs Commit 9fb10b892 ("fixup! reestablish: Send announcement sigs when asked") converted send_channel_announce_sigs() from bool to void but removed all early-return statements. This causes the function to unconditionally send announcement_signatures even when: 1. The channel hasn't finished reestablishment yet 2. The HSM returned invalid signatures (channel_internal_error was called but execution continued) Additionally, channel_gossip_channel_reestablished() called send_channel_announce_sigs() directly when announcement_sigs_requested was true, bypassing the gossip state machine. This sent unexpected announcement_signatures to the peer, causing the remote side to drop the connection (channeld exits with status_peer_connection_lost/62208). The bug only manifests between two v26.04rc1 peers because only v26.04rc1 sets the my_current_funding_locked TLV with retransmit_flags that triggers announcement_sigs_requested=true. Fix by: - Restoring early returns in send_channel_announce_sigs() - Moving the sent_sigs guard into send_channel_announce_sigs() itself - Removing the send_channel_announce_sigs_once() wrapper - Removing the announcement_sigs_requested bypass that called send_channel_announce_sigs() outside the state machine Fixes: https://github.com/ElementsProject/lightning/issues/8978 Changelog-Fixed: Peers no longer disconnect immediately after channel reestablishment. --- lightningd/channel_control.c | 4 ++-- lightningd/channel_gossip.c | 39 ++++++++++++++++-------------------- lightningd/channel_gossip.h | 3 +-- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index 9735e72a6699..7e488233f263 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -1360,7 +1360,7 @@ static void peer_channeld_reestablished(struct channel *channel, const u8* msg) "bad channeld_reestablished %s", tal_hex(channel, msg)); - channel_gossip_channel_reestablished(channel, announcement_sigs_requested); + channel_gossip_channel_reestablished(channel); } void channel_fallen_behind(struct channel *channel) @@ -1976,7 +1976,7 @@ bool peer_start_channeld(struct channel *channel, /* "Reestablished" if we've just opened. */ if (!reconnected) - channel_gossip_channel_reestablished(channel, false); + channel_gossip_channel_reestablished(channel); /* FIXME: DTODO: Use a pointer to a txid instead of zero'ing one out. */ memset(&txid, 0, sizeof(txid)); diff --git a/lightningd/channel_gossip.c b/lightningd/channel_gossip.c index cb5a42d83489..ea6ac3e03faf 100644 --- a/lightningd/channel_gossip.c +++ b/lightningd/channel_gossip.c @@ -698,8 +698,13 @@ static void send_channel_announce_sigs(struct channel *channel) const u8 *ca, *msg; /* Wait until we've exchanged reestablish messages */ - if (!channel->reestablished) + if (!channel->reestablished) { log_debug(channel->log, "channel_gossip: not sending channel_announcement_sigs until reestablished"); + return; + } + + if (cg->sent_sigs) + return; ca = create_channel_announcement(tmpctx, channel, *channel->scid, NULL, NULL, NULL, NULL); @@ -714,13 +719,17 @@ static void send_channel_announce_sigs(struct channel *channel) /* Double-check that HSM gave valid signatures. */ sha256_double(&hash, ca + offset, tal_count(ca) - offset); - if (!check_signed_hash(&hash, &local_node_sig, &ld->our_pubkey)) + if (!check_signed_hash(&hash, &local_node_sig, &ld->our_pubkey)) { channel_internal_error(channel, "HSM returned an invalid node signature"); + return; + } - if (!check_signed_hash(&hash, &local_bitcoin_sig, &channel->local_funding_pubkey)) + if (!check_signed_hash(&hash, &local_bitcoin_sig, &channel->local_funding_pubkey)) { channel_internal_error(channel, "HSM returned an invalid bitcoin signature"); + return; + } msg = towire_announcement_signatures(NULL, &channel->cid, *channel->scid, @@ -729,16 +738,6 @@ static void send_channel_announce_sigs(struct channel *channel) cg->sent_sigs = true; } -static void send_channel_announce_sigs_once(struct channel *channel) -{ - struct channel_gossip *cg = channel->channel_gossip; - - if (cg->sent_sigs) - return; - - send_channel_announce_sigs(channel); -} - /* Sends channel_announcement */ static void send_channel_announcement(struct channel *channel) { @@ -830,7 +829,7 @@ static void set_gossip_state(struct channel *channel, case CGOSSIP_ANNOUNCED: /* In case this snuck up on us (fast confirmations), * make sure we sent sigs */ - send_channel_announce_sigs_once(channel); + send_channel_announce_sigs(channel); /* BOLT #7: * A recipient node: @@ -898,7 +897,7 @@ static void update_gossip_state(struct channel *channel) return; case CGOSSIP_WAITING_FOR_MATCHING_PEER_SIGS: case CGOSSIP_WAITING_FOR_ANNOUNCE_DEPTH: - send_channel_announce_sigs_once(channel); + send_channel_announce_sigs(channel); /* fall thru */ case CGOSSIP_WAITING_FOR_SCID: case CGOSSIP_PRIVATE: @@ -1006,7 +1005,7 @@ void channel_gossip_got_announcement_sigs(struct channel *channel, send_our_sigs: /* This only works once, so we won't spam them. */ - send_channel_announce_sigs_once(channel); + send_channel_announce_sigs(channel); } /* Short channel id changed (splice, or reorg). */ @@ -1202,8 +1201,7 @@ static void channel_reestablished_stable(struct channel *channel) } /* Peer has connected and successfully reestablished channel. */ -void channel_gossip_channel_reestablished(struct channel *channel, - bool announcement_sigs_requested) +void channel_gossip_channel_reestablished(struct channel *channel) { channel->reestablished = true; tal_free(channel->stable_conn_timer); @@ -1221,9 +1219,6 @@ void channel_gossip_channel_reestablished(struct channel *channel, /* We can re-xmit sigs once per reconnect */ channel->channel_gossip->sent_sigs = false; - if (announcement_sigs_requested) - send_channel_announce_sigs(channel); - /* BOLT #7: * - Upon reconnection (once the above timing requirements have * been met): @@ -1244,7 +1239,7 @@ void channel_gossip_channel_reestablished(struct channel *channel, check_channel_gossip(channel); return; case CGOSSIP_WAITING_FOR_MATCHING_PEER_SIGS: - send_channel_announce_sigs_once(channel); + send_channel_announce_sigs(channel); /* fall thru */ case CGOSSIP_PRIVATE: case CGOSSIP_WAITING_FOR_ANNOUNCE_DEPTH: diff --git a/lightningd/channel_gossip.h b/lightningd/channel_gossip.h index b6fae02a204e..6b8e4e203262 100644 --- a/lightningd/channel_gossip.h +++ b/lightningd/channel_gossip.h @@ -43,8 +43,7 @@ void channel_gossip_update_from_gossipd(struct channel *channel, void channel_gossip_init_done(struct lightningd *ld); /* Peer has connected and successfully reestablished channel. */ -void channel_gossip_channel_reestablished(struct channel *channel, - bool announcement_sigs_requested); +void channel_gossip_channel_reestablished(struct channel *channel); /* Peer has disconnected */ void channel_gossip_channel_disconnect(struct channel *channel); From ce0cec70b966fb2b81a93dcb0a604cb8fd9838c8 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Wed, 25 Mar 2026 14:54:10 +0100 Subject: [PATCH 2/3] tests: add regression test for reestablish announcement_sigs bug Verify that a channel stays connected after disconnect/reconnect when it has already fully exchanged announcement_signatures. The bug in send_channel_announce_sigs() caused unconditional sending of announcement_signatures on reconnect, making the peer drop the connection. Fixes: https://github.com/ElementsProject/lightning/issues/8978 --- tests/test_gossip.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/test_gossip.py b/tests/test_gossip.py index 30f94174ded9..92ab40e44705 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -116,6 +116,41 @@ def count_active(node): wait_for(lambda: count_active(l2) == 2) +def test_reestablish_announcement_sigs(node_factory, bitcoind): + """Regression test: peers must not disconnect after reestablishing + a channel that has already exchanged announcement_signatures. + + A bug in send_channel_announce_sigs() caused it to unconditionally + send announcement_signatures on reconnect (missing early returns), + which made the remote peer drop the connection. + See: https://github.com/ElementsProject/lightning/issues/8978 + """ + opts = {'dev-no-reconnect': None, 'may_reconnect': True} + l1, l2 = node_factory.get_nodes(2, opts=opts) + + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + scid, _ = l1.fundchannel(l2, 10**6) + bitcoind.generate_block(6) + + # Wait for channel to be fully announced (both sides exchanged sigs) + l1.wait_channel_active(scid) + l2.wait_channel_active(scid) + + # Disconnect and reconnect - channel should stay up + l1.rpc.disconnect(l2.info['id'], force=True) + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + + # Wait for reestablishment + l1.daemon.wait_for_log('channel_gossip: reestablished') + + # Channel must remain connected (the bug caused immediate disconnect) + import time + time.sleep(2) + channels = l1.rpc.listpeerchannels()['channels'] + assert len(channels) == 1 + assert channels[0]['peer_connected'] is True + + def test_announce_address(node_factory, bitcoind): """Make sure our announcements are well formed.""" From 4fd8efba7874596f31f924ca2560ded20cefff8d Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Thu, 26 Mar 2026 15:39:33 +0100 Subject: [PATCH 3/3] fix: address PR review feedback --- tests/test_gossip.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_gossip.py b/tests/test_gossip.py index 92ab40e44705..a2009cf67e44 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -144,7 +144,6 @@ def test_reestablish_announcement_sigs(node_factory, bitcoind): l1.daemon.wait_for_log('channel_gossip: reestablished') # Channel must remain connected (the bug caused immediate disconnect) - import time time.sleep(2) channels = l1.rpc.listpeerchannels()['channels'] assert len(channels) == 1