Skip to content

BOLT 7 SHOULD miss: gossip_queries peer never receives channel_announcement/channel_update for channels it is not a counterparty of #288

Description

@vincenzopalazzo

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
  1. connect lp1-CLN, CLN-lk1, lp2-lk1
  2. open public c1 (lp1->CLN), c2 (CLN->lk1), c3 (lp2->lk1); mine 6; wait ready
  3. lp1->lp2 and lp2->lp1 (bolt11 / keysend) fail SendingFailed(RouteNotFound) for ~9 min
  4. 1-hop lp1->CLN (BOLT12) succeeds the whole time
  5. 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:

  1. Does lk1 ever log Handling BroadcastChannelAnnouncement for c2?
  2. Does handle_channel_announcement fail (GossipVerifier / no UTXO lookup on this config) so the broadcast is dropped?
  3. 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?
  4. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions