gossipd: make sure we correctly move node announcement when *no* channel preceeds it in the gossip store.
What changed, and why it matters
This commit fixes a logic bug in Core Lightning's gossip daemon. The condition for moving a 'node announcement' record in the gossip store was accidentally inverted, so it was moved all the time instead of only when needed. This caused unnecessary growth of the gossip store and could hide node announcements from the listnodes() RPC result. It is a reliability/availability bug, not a direct funds-loss vulnerability, and there is no evidence it was disclosed as a security issue or credited to an external researcher.
Apply the patch to correct the inverted condition. Monitor gossip store size and listnodes() completeness after upgrade. No emergency response is indicated; the issue is a correctness/availability bug rather than an exploitable security vulnerability.
Security signals we found
Logic inversion bug in gossip store maintenance
Can cause node announcements to be missing from listnodes() RPC
Causes gossip store bloat due to unnecessary record movement
No direct cryptographic, monetary, or remote-code-execution signal present
Evidence from the diff
In gossipd/gossmap_manage.c, remove_channel() checks whether a removed channel was the last channel_announcement preceding a node_announcement. The original code used ‘&& any_cannounce_preceeds_offset(…)’ when it should have been ‘&& !any_cannounce_preceeds_offset(…)’. The inverted test caused the node_announcement to be relocated unconditionally whenever the channel offset was less than the node announcement offset, bloating the gossip store and failing to relocate the announcement in the genuine no-preceding-channel case. The fix flips the boolean and removes the xfail marker from test_gossmap_lost_node.
Changed components
gossipd/gossmap_manage.ctests/test_gossip.pyInspect captured patch +1 / −2
diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c
index 3227465d..fb9a1d85 100644
--- a/gossipd/gossmap_manage.c
+++ b/gossipd/gossmap_manage.c
@@ -300,7 +300,7 @@ static void remove_channel(struct gossmap_manage *gm,
/* Maybe this was the last channel_announcement which preceeded node_announcement? */
if (chan->cann_off < node->nann_off
- && any_cannounce_preceeds_offset(gossmap, node, chan, node->nann_off)) {
+ && !any_cannounce_preceeds_offset(gossmap, node, chan, node->nann_off)) {
const u8 *nannounce;
u32 timestamp;
diff --git a/tests/test_gossip.py b/tests/test_gossip.py
index 76c3a905..df1717d7 100644
--- a/tests/test_gossip.py
+++ b/tests/test_gossip.py
@@ -2372,7 +2372,6 @@ def test_incoming_unreasonable(node_factory):
l3.rpc.listincoming()
-@pytest.mark.xfail(strict=True)
def test_gossmap_lost_node(node_factory, bitcoind):
l1, l2, l3, l4 = node_factory.line_graph(4, wait_for_announce=True)
Why this scored 32/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.