pytest: add test to demonstrate gossip_store misordering node announcements.
What changed, and why it matters
This commit only adds a new test that demonstrates a bug: after restarting a Core Lightning node, one node's announcement information can disappear from the local network view. The test is marked as expected to fail, so it documents the problem rather than fixing it. There is no immediate security exploit here, but it shows a reliability issue in how the node stores and reloads network gossip data.
Treat this as a bug-report test. A follow-up fix should ensure gossipd correctly reorders or reprocesses node_announcements relative to channel_announcements after restart, and the xfail marker should be removed once the underlying issue is resolved. No immediate patch or deployment action is required from this commit alone.
Security signals we found
gossip_store message ordering defect
node_announcement dropped on restart
gossmap ignores node announcement without prior channel_announcement
test marked xfail documenting unfixed behavior
Evidence from the diff
The commit adds test_gossmap_lost_node in tests/test_gossip.py. It creates a 4-node line graph, closes a channel, waits for the close to propagate, records l1’s view of channels and nodes, restarts l1, and asserts that the post-restart gossip view matches the pre-restart view. The test is decorated with @pytest.mark.xfail(strict=True) because it currently fails: a node_announcement is lost on restart when a channel_announcement does not precede it in the gossip_store, causing gossmap to ignore the node announcement. The commit does not include any fix.
Changed components
tests/test_gossip.pygossipd gossip store handlinggossmapInspect captured patch +19 / −0
diff --git a/tests/test_gossip.py b/tests/test_gossip.py
index 44ceba54..76c3a905 100644
--- a/tests/test_gossip.py
+++ b/tests/test_gossip.py
@@ -2370,3 +2370,22 @@ def test_incoming_unreasonable(node_factory):
wait_for(lambda: [c['updates']['remote']['fee_base_msat'] for c in l3.rpc.listpeerchannels()['channels']] == [100000000, 100000000])
l3.restart()
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)
+
+ scid23 = only_one(l2.rpc.listpeerchannels(l3.info['id'])['channels'])['short_channel_id']
+ l2.rpc.close(l3.info['id'])
+ bitcoind.generate_block(13, wait_for_mempool=1)
+ wait_for(lambda: l1.rpc.listchannels(scid23) == {'channels': []})
+
+ pre_channels = l1.rpc.listchannels()
+ pre_nodes = l1.rpc.listnodes()
+ l1.restart()
+ post_channels = l1.rpc.listchannels()
+ post_nodes = l1.rpc.listnodes()
+
+ assert post_channels == pre_channels
+ assert post_nodes == pre_nodes
Why this scored 28/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.