P.S: This is coming out from an AI simulation of different nodes and also the bug report if filled made by the AI but first glance looks legit
Summary
A gossip_queries peer that is not a channel counterparty does not receive channel_announcement / channel_update from ldk-server (ldk-node / rust-lightning PeerManager). In a 3-implementation regtest cluster this leaves the far lampo node RouteNotFound for the whole run (~9 minutes after every public channel is ready). Restarting that lampo node (fresh gossip_timestamp_filter + dump against a graph that now exists) converges in ~1s.
This is a BOLT 7 SHOULD miss on the current spec. There is no open lightning/bolts PR that changes this (taproot gossip #1059 is unrelated; #641 which wanted to drop the dump-on-filter was closed unmerged).
The code path is rust-lightning PeerManager (lightning 0.3), which ldk-server uses via ldk-node. Filing here because that is the binary we ran; happy to move to rust-lightning / ldk-node if you prefer.
Topology
lp1 (lampo) --c1-- CLN --c2-- lk1 (ldk-server / ldk-node) --c3-- lp2 (lampo)
All three channels public, 1M sat, push so the path is liquid both ways. Connect before fundchannel. Mine 6. Every side reports ready.
lp2's only peer is lk1. For lp2 to route to lp1, lk1 must give lp2 c2 (CLN--lk1). lp2 already has c3 (it is a party). It never learns a foreign updated channel.
Repro
Harness: lampo.rs simulations/cln-interop.sh
- CLN v26.06.7
- ldk-server (ldk-node, P2P gossip --
pathfinding_scores_source_url = "" is scores, not RGS)
- lampo on LDK 0.3.0-beta1
- connect lp1-CLN, CLN-lk1, lp2-lk1
- open public c1 (lp1->CLN), c2 (CLN->lk1), c3 (lp2->lk1); mine 6; wait ready
- lp1->lp2 and lp2->lp1 (bolt11 / keysend) fail
SendingFailed(RouteNotFound) for ~9 min
- 1-hop lp1->CLN (BOLT12) succeeds the whole time
- restart lp1 -> first multi-hop pay succeeds at t+1s
Last clean run (sim-run-cln-612c): 12 OK / 7 FAIL. Failures are exactly the 3-hop matrix (G-gossip-settled, P01-P05, B02). B01 (lp1 pays CLN offer) OK.
Evidence from lp2 (only peer = lk1)
After ChannelReady on c3, lampo logs (target lampo-gossip):
graph_channels=1 our_announced=1 our_in_graph=1 updated=1 foreign_updated=0
lp2 then disconnects+reconnects lk1 six times (stall probe, 30s spacing) so rust-lightning re-runs P2PGossipSync::peer_connected on a live route_handler and sends a fresh gossip_timestamp_filter (first 5 connections: first_timestamp = now - 2 weeks, timestamp_range = u32::MAX). foreign_updated stays 0. Payments stay RouteNotFound.
lp1 (peered only with CLN) is a separate CLN-relay question. lp2 has no CLN in the path to gossip: if c2 is missing on lp2, lk1 did not send it.
Lampo traces GOSSIP Sending ChannelAnnouncement/ChannelUpdate for its own channels; the wire from lampo is fine. After we stopped building a second P2PGossipSync in listen() (the ChannelReady re-query used to queue filters on a dead instance), the filter really goes on the socket.
BOLT 7 (current master, no open PR changing this)
07-routing-gossip.md gossip_timestamp_filter:
A node which wants any gossip messages has to send this, otherwise no gossip messages would be received.
Receiver:
- SHOULD send all gossip whose
timestamp is in [first_timestamp, first_timestamp + timestamp_range).
- MAY wait for the next outgoing gossip flush (BOLT 7: once every 60s). That is seconds, not nine minutes.
- SHOULD send gossip messages as it generates them regardless of
timestamp.
- Relayed gossip: SHOULD restrict to the window.
- MUST NOT send a
channel_announcement with no corresponding channel_updates.
Also:
MUST NOT relay any gossip messages it did not generate itself, unless explicitly requested.
MUST not send gossip it did not generate itself, until it receives gossip_timestamp_filter.
The filter is the explicit request. c2 is gossip lk1 generated (it is a party), so the "unless requested" / "until filter" lines do not apply to withholding it from lp2.
Closed spec history, so we do not wait on it:
- bolts#641 "don't send historical updates for
gossip_timestamp_filter" -- closed, not merged. Even that PR still required new gossip after connect.
- bolts#981 (merged) notes the sync method is a bad design. Commentary, not a license to drop relay.
- bolts#980 already recorded CLN vs eclair inconsistency on dump-vs-filter. Eclair still has ACINQ/eclair#2243 open ("Dump graph when receiving
gossip_timestamp_filter").
- bolts#1059 taproot gossip -- different messages, not this.
Where we think it is in LDK (lightning 0.3 peer_handler.rs)
On receiving GossipTimestampFilter, if first_timestamp is older than six hours (LDK senders use two weeks for the first FULL_SYNCS_TO_REQUEST=5 peers):
peer_lock.sync_status = InitSyncTracker::ChannelsSyncing(0);
While ChannelsSyncing(c), live relay of non-own channels is gated:
fn should_forward_channel_announcement(&self, channel_id: u64) -> bool {
if gossip_queries && !sent_gossip_timestamp_filter { return false; }
match self.sync_status {
NoSyncRequested => true,
ChannelsSyncing(i) => channel_id < i, // not yet walked in the backfill
NodesSyncing(_) => true,
}
}
Backfill is get_next_channel_announcement(c) over the current graph. If the graph is empty when the filter arrives (lp2 connected before c2/c3 existed), that walk returns None immediately and the peer is advanced toward NoSyncRequested without ever sending later channels.
Own-channel broadcast (BroadcastChannelAnnouncement) skips the should_forward gate, but:
match route_handler.handle_channel_announcement(None, &msg) {
Ok(_) | Err(IgnoreDuplicateGossip) => forward_broadcast_msg(...),
_ => {}, // validation / pending UTXO -> not forwarded to anyone
}
and forward_broadcast_msg does not send channel_announcement to the two counterparties. That is correct for c3->lp2 (lp2 is a party). It is not a reason to skip c2->lp2.
Please check on a 3-node cluster with ldk-server in the middle:
- Does lk1 ever log
Handling BroadcastChannelAnnouncement for c2?
- Does
handle_channel_announcement fail (GossipVerifier / no UTXO lookup on this config) so the broadcast is dropped?
- After lp2's 2-week filter, does lk1 stay in
ChannelsSyncing and drop live relay of c2, or jump to NoSyncRequested with an empty walk and then never re-dump when c2 appears?
- After lp2 disconnects+reconnects (new filter, graph now non-empty), does the dump include c2 with both
channel_update directions? BOLT 7 MUST NOT send announcement-only entries; an announcement without updates is not routable (graph_channels=3 + RouteNotFound is that shape).
Expected
A peer that sent gossip_timestamp_filter / gossip_queries should receive:
- historical gossip in the timestamp window (SHOULD, current spec),
- new
channel_announcement + both channel_updates as lk1 generates them, including channels whose other endpoint is a third node (SHOULD, "as it generates them regardless of timestamp").
Seen with
P.S: This is coming out from an AI simulation of different nodes and also the bug report if filled made by the AI but first glance looks legit
Summary
A
gossip_queriespeer that is not a channel counterparty does not receivechannel_announcement/channel_updatefrom ldk-server (ldk-node / rust-lightningPeerManager). In a 3-implementation regtest cluster this leaves the far lampo nodeRouteNotFoundfor the whole run (~9 minutes after every public channel is ready). Restarting that lampo node (freshgossip_timestamp_filter+ dump against a graph that now exists) converges in ~1s.This is a BOLT 7 SHOULD miss on the current spec. There is no open lightning/bolts PR that changes this (taproot gossip #1059 is unrelated; #641 which wanted to drop the dump-on-filter was closed unmerged).
The code path is rust-lightning
PeerManager(lightning0.3), which ldk-server uses via ldk-node. Filing here because that is the binary we ran; happy to move to rust-lightning / ldk-node if you prefer.Topology
All three channels public, 1M sat, push so the path is liquid both ways. Connect before
fundchannel. Mine 6. Every side reports ready.lp2's only peer is lk1. For lp2 to route to lp1, lk1 must give lp2 c2 (CLN--lk1). lp2 already has c3 (it is a party). It never learns a foreign updated channel.
Repro
Harness: lampo.rs
simulations/cln-interop.shpathfinding_scores_source_url = ""is scores, not RGS)SendingFailed(RouteNotFound)for ~9 minLast clean run (
sim-run-cln-612c): 12 OK / 7 FAIL. Failures are exactly the 3-hop matrix (G-gossip-settled, P01-P05, B02). B01 (lp1 pays CLN offer) OK.Evidence from lp2 (only peer = lk1)
After
ChannelReadyon c3, lampo logs (targetlampo-gossip):lp2 then disconnects+reconnects lk1 six times (stall probe, 30s spacing) so rust-lightning re-runs
P2PGossipSync::peer_connectedon a liveroute_handlerand sends a freshgossip_timestamp_filter(first 5 connections:first_timestamp = now - 2 weeks,timestamp_range = u32::MAX).foreign_updatedstays 0. Payments stayRouteNotFound.lp1 (peered only with CLN) is a separate CLN-relay question. lp2 has no CLN in the path to gossip: if c2 is missing on lp2, lk1 did not send it.
Lampo traces
GOSSIP Sending ChannelAnnouncement/ChannelUpdatefor its own channels; the wire from lampo is fine. After we stopped building a secondP2PGossipSyncinlisten()(the ChannelReady re-query used to queue filters on a dead instance), the filter really goes on the socket.BOLT 7 (current
master, no open PR changing this)07-routing-gossip.mdgossip_timestamp_filter:Receiver:
timestampis in[first_timestamp, first_timestamp + timestamp_range).timestamp.channel_announcementwith no correspondingchannel_updates.Also:
The filter is the explicit request. c2 is gossip lk1 generated (it is a party), so the "unless requested" / "until filter" lines do not apply to withholding it from lp2.
Closed spec history, so we do not wait on it:
gossip_timestamp_filter" -- closed, not merged. Even that PR still required new gossip after connect.gossip_timestamp_filter").Where we think it is in LDK (
lightning0.3peer_handler.rs)On receiving
GossipTimestampFilter, iffirst_timestampis older than six hours (LDK senders use two weeks for the firstFULL_SYNCS_TO_REQUEST=5peers):While
ChannelsSyncing(c), live relay of non-own channels is gated:Backfill is
get_next_channel_announcement(c)over the current graph. If the graph is empty when the filter arrives (lp2 connected before c2/c3 existed), that walk returnsNoneimmediately and the peer is advanced towardNoSyncRequestedwithout ever sending later channels.Own-channel broadcast (
BroadcastChannelAnnouncement) skips theshould_forwardgate, but:and
forward_broadcast_msgdoes not sendchannel_announcementto the two counterparties. That is correct for c3->lp2 (lp2 is a party). It is not a reason to skip c2->lp2.Please check on a 3-node cluster with ldk-server in the middle:
Handling BroadcastChannelAnnouncementfor c2?handle_channel_announcementfail (GossipVerifier / no UTXO lookup on this config) so the broadcast is dropped?ChannelsSyncingand drop live relay of c2, or jump toNoSyncRequestedwith an empty walk and then never re-dump when c2 appears?channel_updatedirections? BOLT 7 MUST NOT send announcement-only entries; an announcement without updates is not routable (graph_channels=3+RouteNotFoundis that shape).Expected
A peer that sent
gossip_timestamp_filter/gossip_queriesshould receive:channel_announcement+ bothchannel_updates as lk1 generates them, including channels whose other endpoint is a third node (SHOULD, "as it generates them regardless of timestamp").Seen with
751b368(local), ldk-node P2P gossip