@@ -63,6 +63,14 @@ type ChannelGraph struct {
6363 cancel fn.Option [context.CancelFunc ]
6464}
6565
66+ // preferHighestNodeDirectedChanneler is implemented by stores that can stream
67+ // cross-version node-directed channel traversals directly.
68+ type preferHighestNodeDirectedChanneler interface {
69+ ForEachNodeDirectedChannelPreferHighest (ctx context.Context ,
70+ node route.Vertex , cb func (channel * DirectedChannel ) error ,
71+ reset func ()) error
72+ }
73+
6674// NewChannelGraph creates a new ChannelGraph instance with the given backend.
6775func NewChannelGraph (v1Store Store ,
6876 options ... ChanGraphOption ) (* ChannelGraph , error ) {
@@ -238,8 +246,9 @@ func (c *ChannelGraph) populateCache(ctx context.Context) error {
238246 for _ , v := range []lnwire.GossipVersion {
239247 gossipV1 , gossipV2 ,
240248 } {
241- // TODO(elle): If we have both v1 and v2 entries for the same
242- // node/channel, prefer v2 when merging.
249+ // We iterate v1 first, then v2. Since AddNodeFeatures and
250+ // AddChannel overwrite on key collision, v2 data naturally
251+ // takes precedence when both versions exist.
243252 err := c .db .ForEachNodeCacheable (ctx , v ,
244253 func (node route.Vertex ,
245254 features * lnwire.FeatureVector ) error {
@@ -299,12 +308,59 @@ func (c *ChannelGraph) ForEachNodeDirectedChannel(ctx context.Context,
299308 return c .cache .graphCache .ForEachChannel (node , cb )
300309 }
301310
302- // TODO(elle): once the no-cache path needs to support
303- // pathfinding across gossip versions, this should iterate
304- // across all versions rather than defaulting to v1.
305- return c .db .ForEachNodeDirectedChannel (
306- ctx , gossipV1 , node , cb , reset ,
307- )
311+ if db , ok := c .db .(preferHighestNodeDirectedChanneler ); ok {
312+ return db .ForEachNodeDirectedChannelPreferHighest (
313+ ctx , node , cb , reset ,
314+ )
315+ }
316+
317+ // Iterate across all gossip versions (highest first) so that
318+ // channels announced via v2 are preferred over v1. We buffer
319+ // results and deliver them at the end so that ExecTx retries
320+ // within a single version don't corrupt the caller's state or
321+ // lose channels from already-committed versions.
322+ seen := make (map [uint64 ]struct {})
323+ var allChannels []* DirectedChannel
324+
325+ for _ , v := range []lnwire.GossipVersion {gossipV2 , gossipV1 } {
326+ prevLen := len (allChannels )
327+ err := c .db .ForEachNodeDirectedChannel (
328+ ctx , v , node ,
329+ func (channel * DirectedChannel ) error {
330+ if _ , ok := seen [channel .ChannelID ]; ok {
331+ return nil
332+ }
333+ seen [channel .ChannelID ] = struct {}{}
334+
335+ allChannels = append (allChannels , channel )
336+
337+ return nil
338+ },
339+ func () {
340+ // On ExecTx retry, undo this version's
341+ // additions while keeping channels from
342+ // earlier (already committed) versions.
343+ for _ , ch := range allChannels [prevLen :] {
344+ delete (seen , ch .ChannelID )
345+ }
346+ allChannels = allChannels [:prevLen ]
347+ },
348+ )
349+ if err != nil &&
350+ ! errors .Is (err , ErrVersionNotSupportedForKVDB ) {
351+
352+ return err
353+ }
354+ }
355+
356+ // Deliver the collected channels to the caller.
357+ for _ , ch := range allChannels {
358+ if err := cb (ch ); err != nil {
359+ return err
360+ }
361+ }
362+
363+ return nil
308364}
309365
310366// FetchNodeFeatures returns the features of the given node. If no features are
@@ -320,7 +376,22 @@ func (c *ChannelGraph) FetchNodeFeatures(ctx context.Context,
320376 return c .cache .graphCache .GetFeatures (node ), nil
321377 }
322378
323- return c .db .FetchNodeFeatures (ctx , lnwire .GossipVersion1 , node )
379+ // Try v2 first, fall back to v1 if the v2 features are empty.
380+ for _ , v := range []lnwire.GossipVersion {gossipV2 , gossipV1 } {
381+ features , err := c .db .FetchNodeFeatures (ctx , v , node )
382+ if errors .Is (err , ErrVersionNotSupportedForKVDB ) {
383+ continue
384+ }
385+ if err != nil {
386+ return nil , err
387+ }
388+
389+ if ! features .IsEmpty () {
390+ return features , nil
391+ }
392+ }
393+
394+ return lnwire .EmptyFeatureVector (), nil
324395}
325396
326397// GraphSession will provide the call-back with access to a NodeTraverser
0 commit comments