Skip to content

graph/db: cross-version graph Store queries#10714

Open
ellemouton wants to merge 6 commits intolightningnetwork:masterfrom
ellemouton:g175-graph-db-cross-version-v2
Open

graph/db: cross-version graph Store queries#10714
ellemouton wants to merge 6 commits intolightningnetwork:masterfrom
ellemouton:g175-graph-db-cross-version-v2

Conversation

@ellemouton
Copy link
Copy Markdown
Collaborator

Summary

This PR makes the graph Store interface cross-version so that ForEachNode, ForEachChannel, and ForEachNodeDirectedChannel work across gossip v1 and v2, returning one preferred entry per pub_key/SCID. It also adds PreferHighest fetch helpers and GetVersions queries so callers can retrieve channels without knowing which gossip version announced them.

Part of the gossip v2 epic: #10293
Spun off from: #10656

Design

Two new materialised mapping tables are introduced:

  • graph_preferred_nodes — one row per unique pub_key, pointing at the best graph_nodes row. Priority: v2 announced > v1 announced > v2 shell > v1 shell.
  • graph_preferred_channels — one row per unique SCID, pointing at the best graph_channels row. Priority: v2 with policies > v1 with policies > v2 bare > v1 bare.

These tables are maintained on every write path (AddNode, AddChannelEdge, UpdateEdgePolicy, DeleteNode, DeleteChannelEdges, PruneGraphNodes) via UpsertPreferredNode/UpsertPreferredChannel SQL queries, and seeded by a migration for existing data.

Changes by commit

  1. sqldb: add preferred-node and preferred-channel mapping tables — migration 000014 creating the tables and populating them from existing data.
  2. graph/db: add PreferHighest fetch methods and GetVersions queries — new Store interface methods: FetchChannelEdgesByIDPreferHighest, FetchChannelEdgesByOutpointPreferHighest, GetVersionsBySCID, GetVersionsByOutpoint. KV implementations delegate to v1.
  3. graph/db: wire preferred-table maintenance into write paths — calls UpsertPreferredNode/UpsertPreferredChannel on all SQL write paths.
  4. graph/db: make ForEachNode and ForEachChannel cross-version — removes the GossipVersion parameter from ForEachNode and ForEachChannel on the Store interface, replacing the underlying queries with preferred-table-backed paginated queries.
  5. graph/db: implement cross-version node traversal — adds ForEachNodeDirectedChannelPreferHighest for cache-disabled SQL path, updates FetchNodeFeatures to prefer v2 features with v1 fallback.
  6. docs: add release note

Performance

Benchmarked on Apple M1 Pro against a mainnet v1-only graph (16,216 nodes, 51,239 channels). No regressions observed — the preferred-table JOIN adds negligible overhead:

Method Backend Result
ForEachNode native-sqlite ~216 ms/op
ForEachNode native-postgres ~102 ms/op
ForEachChannel native-sqlite ~1,482 ms/op
ForEachChannel native-postgres ~538 ms/op

Test plan

  • New unit tests: TestPreferHighestAndGetVersions, TestPreferHighestForEachNode, TestPreferHighestForEachChannel, TestPreferHighestNodeTraversal, TestPreferHighestNodeDirectedChannelTraversal, TestDeleteNodePreferredRecomputation
  • All existing graph DB tests pass (v1 behaviour preserved)
  • Benchmark confirms no performance regression for v1-only graphs
  • SQL-only tests skipped on KV backend via isSQLDB guard

Add two precomputed mapping tables that track the "best" gossip version
for each unique node (pub_key) and channel (SCID):

 - graph_preferred_nodes:    pub_key  -> node_id
 - graph_preferred_channels: scid     -> channel_id

Priority for nodes: v2 announced > v1 announced > v2 shell > v1 shell.
Priority for channels: v2 with policies > v1 with policies > v2 > v1.

These tables enable simple indexed-join queries for cross-version
traversal (ForEachNode, ForEachChannel, ForEachNodeDirectedChannel)
without expensive per-row COALESCE subqueries. The tables are populated
from existing data during the migration and maintained by upsert/delete
queries on every write path (added in the next commit).
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the graph database layer to support cross-version gossip data. By introducing materialized mapping tables, the system can now automatically select the preferred version of nodes and channels based on defined priority rules. This change simplifies the API for callers and ensures that graph traversal methods consistently return the most relevant data, regardless of whether it was announced via gossip v1 or v2.

Highlights

  • Cross-version Store Interface: Updated the graph Store interface to be version-agnostic, allowing ForEachNode, ForEachChannel, and ForEachNodeDirectedChannel to operate across gossip v1 and v2 seamlessly.
  • Preferred Mapping Tables: Introduced graph_preferred_nodes and graph_preferred_channels materialized tables to maintain the best version of nodes and channels, ensuring efficient cross-version queries.
  • New Fetch Helpers: Added PreferHighest fetch methods and GetVersions queries to enable callers to retrieve the best available data without needing to specify the gossip version.
  • Write Path Maintenance: Integrated automatic updates to the preferred mapping tables within all SQL write paths to ensure data consistency.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the graph Store interface to support cross-version operations, enabling node and channel traversals to work across gossip v1 and v2. It introduces preferred mapping tables in the SQL backend and PreferHighest fetch helpers to prioritize v2 data while falling back to v1. Feedback was provided to implement a feature fallback in the no-cache path for directed channel traversals, ensuring consistency with the FetchNodeFeatures logic by providing the best available feature set when a specific gossip version lacks data.

}
}

toNodeCallback := func() route.Vertex {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In the no-cache path, we should implement a fallback for node features similar to the logic in ChannelGraph.FetchNodeFeatures. This ensures that if a node has v1 features but no v2 features, we still provide the v1 features for pathfinding even if the channel announcement is v2. This maintains consistency between the cache-enabled and cache-disabled code paths.

	// Try v2 first, fall back to v1 if the v2 features are empty. This
	// ensures consistency with FetchNodeFeatures and provides the best
	// available feature set for the node.
	preferredFeatures := featuresByVersion[gossipV2]
	if preferredFeatures.IsEmpty() {
		preferredFeatures = featuresByVersion[gossipV1]
	}

	toNodeCallback := func() route.Vertex {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current behaviour is actually more correct here. Tying ToNodeFeatures to the channel's gossip version ensures the features match the protocol version of the channel advertisement. A v2 preferred channel should use v2 features, and a v1 preferred channel should use v1 features.

Using a single "best" feature set across all channels would mean a v1 channel could get v2 features assigned, which is a version mismatch.

The underlying ambiguity (empty features vs absent node for a given version) is a known limitation — the proper fix is to distinguish those two cases at the FetchNodeFeatures layer rather than papering over it here.

Comment on lines +4246 to +4249
cachedInPolicy.ToNodeFeatures =
featuresByVersion[lnwire.GossipVersion(
row.GraphChannel.Version,
)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use the pre-calculated preferredFeatures here to ensure the best available feature set is used for the node, regardless of the channel's gossip version.

Suggested change
cachedInPolicy.ToNodeFeatures =
featuresByVersion[lnwire.GossipVersion(
row.GraphChannel.Version,
)]
cachedInPolicy.ToNodeFeatures = preferredFeatures

Add four new version-agnostic Store methods that return the highest
available gossip version for a channel:

 - FetchChannelEdgesByIDPreferHighest
 - FetchChannelEdgesByOutpointPreferHighest
 - GetVersionsBySCID
 - GetVersionsByOutpoint

The KV store delegates to v1; the SQL store iterates versions from
highest to lowest, returning the first with policies (falling back to
the highest version without).
Add UpsertPreferredNode / DeletePreferredNode / UpsertPreferredChannel
/ DeletePreferredChannel calls to every Store write path so the mapping
tables stay consistent:

 - upsertSourceNode, upsertNode, maybeCreateShellNode, DeleteNode
 - insertChannel, updateChanEdgePolicy, DeleteChannelEdges
 - pruneGraphNodes
Drop the GossipVersion parameter from ForEachNode and ForEachChannel.
Both methods now iterate across all gossip versions, yielding one result
per unique pub_key or SCID using the preferred mapping tables for
pagination.

Wire ChannelGraph.FetchChannelEdgesByID and FetchChannelEdgesByOutpoint
through the PreferHighest variants. Add GetVersionsBySCID and
GetVersionsByOutpoint wrappers to ChannelGraph.
Update ForEachNodeDirectedChannel and FetchNodeFeatures to work across
gossip versions.

ForEachNodeDirectedChannel now checks for a
preferHighestNodeDirectedChanneler interface on the store (implemented
by SQLStore) to stream cross-version directed channels in a single
paginated query against the preferred-channel mapping table. The
fallback path iterates v2 then v1, buffering results to deduplicate by
SCID.

FetchNodeFeatures tries v2 first, falling back to v1 when v2 features
are empty.

The cache population comment is updated to note that v1-then-v2
iteration order naturally gives v2 precedence via key collision
overwrite.
@ellemouton ellemouton force-pushed the g175-graph-db-cross-version-v2 branch from 32f201a to 652f923 Compare April 7, 2026 07:25
@ellemouton ellemouton force-pushed the g175-graph-db-cross-version-v2 branch from 652f923 to 24c9c8d Compare April 7, 2026 07:36
@saubyk saubyk added this to the v0.22.0 milestone Apr 7, 2026
@saubyk saubyk added this to lnd v0.22 Apr 7, 2026
@github-project-automation github-project-automation bot moved this to Backlog in lnd v0.22 Apr 7, 2026
@saubyk saubyk moved this from Backlog to In progress in lnd v0.22 Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants