From 64110cf45933a6dc30fc0af3b74c94d5aaa4a216 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 30 Jun 2025 11:04:35 +0200 Subject: [PATCH] graph/db: let FetchChannelEdgesByID behave as promised The comment of FetchChannelEdgesByID says that if the ErrZombieEdge error is returned, then the ChannelEdgeInfo return parameter will also still be populated and returned (ie, wont be nil). This commit updates the SQLStore implementation of FetchChannelEdgesByID to do this. This is needed to prevent nil dereference panics at any call-sites that depend on the method working as it describes. --- graph/db/graph_test.go | 8 +++++++- graph/db/sql_store.go | 26 +++++++++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go index 918369a9c..02e96dfc9 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -451,8 +451,14 @@ func TestEdgeInsertionDeletion(t *testing.T) { // properly deleted. _, _, _, err = graph.FetchChannelEdgesByOutpoint(&outpoint) require.ErrorIs(t, err, ErrEdgeNotFound) - _, _, _, err = graph.FetchChannelEdgesByID(chanID) + + // Assert that if the edge is a zombie, then FetchChannelEdgesByID + // still returns a populated models.ChannelEdgeInfo as its comment + // description promises. + edge, _, _, err := graph.FetchChannelEdgesByID(chanID) require.ErrorIs(t, err, ErrZombieEdge) + require.NotNil(t, edge) + isZombie, _, _, err := graph.IsZombieEdge(chanID) require.NoError(t, err) require.True(t, isZombie) diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go index 698c47771..5ff8b7a1d 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -1852,20 +1852,29 @@ func (s *SQLStore) FetchChannelEdgesByID(chanID uint64) ( if errors.Is(err, sql.ErrNoRows) { // First check if this edge is perhaps in the zombie // index. - isZombie, err := db.IsZombieChannel( - ctx, sqlc.IsZombieChannelParams{ + zombie, err := db.GetZombieChannel( + ctx, sqlc.GetZombieChannelParams{ Scid: chanIDB[:], Version: int16(ProtocolV1), }, ) - if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return ErrEdgeNotFound + } else if err != nil { return fmt.Errorf("unable to check if "+ "channel is zombie: %w", err) - } else if isZombie { - return ErrZombieEdge } - return ErrEdgeNotFound + // At this point, we know the channel is a zombie, so + // we'll return an error indicating this, and we will + // populate the edge info with the public keys of each + // party as this is the only information we have about + // it. + edge = &models.ChannelEdgeInfo{} + copy(edge.NodeKey1Bytes[:], zombie.NodeKey1) + copy(edge.NodeKey2Bytes[:], zombie.NodeKey2) + + return ErrZombieEdge } else if err != nil { return fmt.Errorf("unable to fetch channel: %w", err) } @@ -1903,7 +1912,10 @@ func (s *SQLStore) FetchChannelEdgesByID(chanID uint64) ( return nil }, sqldb.NoOpReset) if err != nil { - return nil, nil, nil, fmt.Errorf("could not fetch channel: %w", + // If we are returning the ErrZombieEdge, then we also need to + // return the edge info as the method comment indicates that + // this will be populated when the edge is a zombie. + return edge, nil, nil, fmt.Errorf("could not fetch channel: %w", err) }