graph/db: add VersionedGraph routing/cache methods
What changed, and why it matters
This commit adds wrapper methods to a new 'VersionedGraph' type so the existing channel graph can be used by the router and RPC layer even after internal interfaces gained a 'version' parameter. It also wires up that wrapper in the main server and RPC server. There is no obvious security bug in the diff itself; it is a refactoring/forward-compatibility change. A TODO comment notes that one fallback path still hardcodes an older version, which could matter later but is not an active vulnerability.
Treat as a normal refactoring commit. Review the noted TODO about hardcoded GossipVersion1 in GraphSession to ensure version-correct behavior before enabling GossipVersion2 production paths. No immediate security patch is indicated by this diff alone.
Security signals we found
Interface-satisfying wrapper methods added to VersionedGraph
Cache-first delegation for FetchNodeFeatures and ForEachNodeDirectedChannel
Server/RPC wiring switched from raw ChannelGraph to VersionedGraph
TODO noting hardcoded GossipVersion1 in fallback GraphSession path
Evidence from the diff
The patch introduces shadow methods on VersionedGraph (FetchNodeFeatures, ForEachNodeDirectedChannel, ForEachNodeCached, ForEachNode, NodeUpdatesInHorizon, ChannelView, GraphSession) so that VersionedGraph satisfies routing.Graph, graphdb.NodeTraverser, and related interfaces. Cache-aware methods prefer c.graphCache; otherwise they delegate to c.db with the baked-in version c.v. server.go and rpcserver.go are updated to pass s.v1Graph (a VersionedGraph wrapping graphDB with GossipVersion1) where routing/session interfaces are expected. A TODO in GraphSession notes the underlying db.GraphSession currently hardcodes GossipVersion1, so v2 support is incomplete. The change is structural and does not introduce new input validation, cryptographic, or authorization logic.
Changed components
graph/db/graph.gograph/db/graph_test.gorouting/router_test.gorpcserver.goserver.goInspect captured patch +97 / −13
diff --git a/graph/db/graph.go b/graph/db/graph.go
index 01683ff..4c90a40 100644
--- a/graph/db/graph.go
+++ b/graph/db/graph.go
@@ -834,6 +834,90 @@ func NewVersionedGraph(c *ChannelGraph,
}
}
+// FetchNodeFeatures returns the features of the given node. If no features are
+// known for the node, an empty feature vector is returned. If the graphCache is
+// available, it will be used instead of the database.
+//
+// NOTE: This is part of the graphdb.NodeTraverser interface.
+func (c *VersionedGraph) FetchNodeFeatures(ctx context.Context,
+ node route.Vertex) (*lnwire.FeatureVector, error) {
+
+ if c.graphCache != nil {
+ return c.graphCache.GetFeatures(node), nil
+ }
+
+ return c.db.FetchNodeFeatures(ctx, c.v, node)
+}
+
+// ForEachNodeDirectedChannel iterates through all channels of a given node,
+// executing the passed callback on the directed edge representing the channel
+// and its incoming policy. If the graphCache is available, it will be used
+// instead of the database.
+//
+// NOTE: This is part of the graphdb.NodeTraverser interface.
+func (c *VersionedGraph) ForEachNodeDirectedChannel(ctx context.Context,
+ node route.Vertex, cb func(channel *DirectedChannel) error,
+ reset func()) error {
+
+ if c.graphCache != nil {
+ return c.graphCache.ForEachChannel(node, cb)
+ }
+
+ return c.db.ForEachNodeDirectedChannel(ctx, c.v, node, cb, reset)
+}
+
+// ForEachNodeCached iterates through all stored vertices/nodes in the graph,
+// delegating to the embedded ChannelGraph.
+func (c *VersionedGraph) ForEachNodeCached(ctx context.Context,
+ withAddrs bool, cb func(ctx context.Context, node route.Vertex,
+ addrs []net.Addr,
+ chans map[uint64]*DirectedChannel) error,
+ reset func()) error {
+
+ return c.ChannelGraph.ForEachNodeCached(ctx, withAddrs, cb, reset)
+}
+
+// ForEachNode iterates through all stored vertices/nodes in the graph.
+func (c *VersionedGraph) ForEachNode(ctx context.Context,
+ cb func(*models.Node) error, reset func()) error {
+
+ return c.db.ForEachNode(ctx, cb, reset)
+}
+
+// NodeUpdatesInHorizon returns all known lightning nodes which have an update
+// timestamp within the passed range.
+func (c *VersionedGraph) NodeUpdatesInHorizon(ctx context.Context,
+ startTime, endTime time.Time,
+ opts ...IteratorOption) iter.Seq2[*models.Node, error] {
+
+ return c.db.NodeUpdatesInHorizon(ctx, startTime, endTime, opts...)
+}
+
+// ChannelView returns the verifiable edge information for each active channel.
+func (c *VersionedGraph) ChannelView(ctx context.Context) ([]EdgePoint,
+ error) {
+
+ return c.db.ChannelView(ctx)
+}
+
+// GraphSession provides the callback with access to a NodeTraverser instance
+// for performing queries against the channel graph. If the graph cache is
+// enabled, the callback receives the VersionedGraph directly (which implements
+// NodeTraverser using the cache). Otherwise a read-only database session is
+// used.
+func (c *VersionedGraph) GraphSession(ctx context.Context,
+ cb func(graph NodeTraverser) error, reset func()) error {
+
+ if c.graphCache != nil {
+ return cb(c)
+ }
+
+ // TODO(elle): the underlying GraphSession currently creates a
+ // NodeTraverser that is hardcoded to GossipVersion1. This needs to be
+ // updated to pass the version through for v2 support.
+ return c.db.GraphSession(ctx, cb, reset)
+}
+
// FetchNode attempts to look up a target node by its identity public key.
func (c *VersionedGraph) FetchNode(ctx context.Context,
nodePub route.Vertex) (*models.Node, error) {
diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go
index b455d8b..58347a1 100644
--- a/graph/db/graph_test.go
+++ b/graph/db/graph_test.go
@@ -1884,9 +1884,9 @@ func testGraphTraversalCacheable(t *testing.T, v lnwire.GossipVersion) {
}
for _, node := range nodes {
- // Query the ChannelGraph which uses the cache to iterate
+ // Query the VersionedGraph which uses the cache to iterate
// through the channels for each node.
- err = graph.ChannelGraph.ForEachNodeDirectedChannel(
+ err = graph.ForEachNodeDirectedChannel(
ctx, node, func(d *DirectedChannel) error {
delete(chanIndex, d.ChannelID)
return nil
@@ -3339,13 +3339,13 @@ func TestStressTestChannelGraphAPI(t *testing.T) {
chanSet := getRandChanSet()
var chanIDs []ChannelUpdateInfo
+ ver := lnwire.GossipVersion1
for _, c := range chanSet {
- chanIDs = append(
- chanIDs,
- ChannelUpdateInfo{
- ShortChannelID: c.id,
- },
- )
+ info := ChannelUpdateInfo{
+ ShortChannelID: c.id,
+ Version: ver,
+ }
+ chanIDs = append(chanIDs, info)
}
_, err := graph.FilterKnownChanIDs(
diff --git a/routing/router_test.go b/routing/router_test.go
index e9c9ed9..e14ac19 100644
--- a/routing/router_test.go
+++ b/routing/router_test.go
@@ -135,7 +135,7 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T,
sourceNode, err := graphInstance.v1Graph.SourceNode(t.Context())
require.NoError(t, err)
sessionSource := &SessionSource{
- GraphSessionFactory: graphInstance.graph,
+ GraphSessionFactory: graphInstance.v1Graph,
SourceNode: sourceNode,
GetLink: graphInstance.getLink,
PathFindingConfig: pathFindingConfig,
@@ -146,7 +146,7 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T,
router, err := New(Config{
SelfNode: sourceNode.PubKeyBytes,
- RoutingGraph: graphInstance.graph,
+ RoutingGraph: graphInstance.v1Graph,
Chain: chain,
Payer: &mockPaymentAttemptDispatcherOld{},
Control: makeMockControlTower(),
diff --git a/rpcserver.go b/rpcserver.go
index ac435fd..0b994a2 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -708,7 +708,7 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server,
if err != nil {
return err
}
- graph := s.graphDB
+ graph := s.v1Graph
routerBackend := &routerrpc.RouterBackend{
SelfNode: selfNode.PubKeyBytes,
diff --git a/server.go b/server.go
index 0359dc7..d4972a3 100644
--- a/server.go
+++ b/server.go
@@ -1016,7 +1016,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
return nil, fmt.Errorf("error getting source node: %w", err)
}
paymentSessionSource := &routing.SessionSource{
- GraphSessionFactory: dbs.GraphDB,
+ GraphSessionFactory: s.v1Graph,
SourceNode: sourceNode,
MissionControl: s.defaultMC,
GetLink: s.htlcSwitch.GetLinkByShortID,
@@ -1047,7 +1047,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
s.chanRouter, err = routing.New(routing.Config{
SelfNode: nodePubKey,
- RoutingGraph: dbs.GraphDB,
+ RoutingGraph: s.v1Graph,
Chain: cc.ChainIO,
Payer: s.htlcSwitch,
Control: s.controlTower,
Why this scored 25/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.