pytest: test for crash when we have dying channels and compact the gossip_store.
What changed, and why it matters
This commit adds a test that reproduces a crash in Core Lightning's gossip daemon. The crash happens when the network's gossip store is compacted while a channel is in a 'dying' state (closing but not yet fully removed). The bug caused internal record offsets to become wrong, leading to a fatal read error and daemon crash. The commit message says the underlying handling was already fixed; this change only adds the regression test.
Verify that the production fix referenced in the commit message is already merged and that this regression test passes. Consider whether any other store-compaction paths (e.g., automatic compaction) could still hit the same stale-offset issue, and add coverage if needed.
Security signals we found
Daemon crash / denial of service in gossipd
Use of stale offsets after gossip store compaction
Regression test for previously fixed crash
Crash occurs during normal channel closure lifecycle
Evidence from the diff
The new test test_gossip_dying_when_compact exercises a crash path in gossipd. When a channel closing transaction reaches 6 confirmations, gossmap_manage_new_block marks the channel as dying. If dev-compact-gossip-store is called before the channel is fully deleted, the old code failed to reset dying-channel offsets, so later operations such as gossip_store_set_flag/gossip_store_del referenced stale offsets and triggered gossip_store_get_with_hdr to abort with ‘can’t read hdr offset …: Success’. The commit message states the fix already exists elsewhere; this commit is only the regression test.
Changed components
gossipd/gossip_store.cgossipd/gossmap_manage.ctests/test_gossip.pyInspect captured patch +19 / −0
diff --git a/tests/test_gossip.py b/tests/test_gossip.py
index c17c7bf2..be548465 100644
--- a/tests/test_gossip.py
+++ b/tests/test_gossip.py
@@ -2500,3 +2500,22 @@ def test_gossmap_lost_node(node_factory, bitcoind):
assert post_channels == pre_channels
assert post_nodes == pre_nodes
+
+
+def test_gossip_dying_when_compact(node_factory, bitcoind):
+ """During PR review, @daywalker90 found a crash when we compacted with a dying channel: our implementation did not reset these, so the offsets were wrong"""
+ l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True)
+
+ assert len(l1.rpc.listchannels()["channels"]) == 4
+
+ # Closed l2->l3, but it's just marked dying by l1.
+ l2.rpc.close(l3.info['id'])
+ bitcoind.generate_block(6, wait_for_mempool=1)
+
+ l1.daemon.wait_for_log('channel .* closing soon due to the funding outpoint being spent')
+ wait_for(lambda: len(l1.rpc.listchannels()["channels"]) == 4)
+ l1.rpc.call("dev-compact-gossip-store")
+
+ # Now actually close it (12 deep)
+ bitcoind.generate_block(11)
+ wait_for(lambda: len(l1.rpc.listchannels()["channels"]) == 2)
Why this scored 58/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.