What changed, and why it matters
This commit is a routine code-quality change: it threads a request-scoped cancellation signal (a 'context') through a database lookup method called HasChannelEdge. Previously some code paths used a blank, never-cancelling context.TODO(), which meant long-running database queries could not be cancelled. The change lets callers cancel or time out the lookup. It is not a security patch in itself and does not fix a known exploitable bug, but it removes a small source of unbounded work that could contribute to denial-of-service conditions.
No immediate action required. Treat as normal maintenance. If deploying, verify that callers which now receive cancellation behaviour still handle errors from cancelled contexts gracefully.
Security signals we found
context propagation improvement
removal of context.TODO() in SQLStore HasChannelEdge
no change to authorization or cryptographic logic
no vendor security framing in commit message
Evidence from the diff
The patch updates the HasChannelEdge method signature across the graph database interfaces and both backend implementations (KVStore and SQLStore) to accept a context.Context. Callers in graph/builder.go now pass the existing request context, while IsKnownEdge and IsZombieEdge use context.TODO() because they do not currently receive a context. The SQLStore no longer creates its own context.TODO(). This enables cancellation/timeout propagation for channel-edge existence checks, replacing an uncancellable placeholder context. There is no change to query logic, access control, or data validation.
Changed components
graph/builder.gograph/db/graph.gograph/db/interfaces.gograph/db/kv_store.gograph/db/sql_store.goInspect captured patch +32 / −34
diff --git a/graph/builder.go b/graph/builder.go
index 64ce013..654a842 100644
--- a/graph/builder.go
+++ b/graph/builder.go
@@ -1050,7 +1050,7 @@ func (b *Builder) addEdge(ctx context.Context, edge *models.ChannelEdgeInfo,
// Prior to processing the announcement we first check if we
// already know of this channel, if so, then we can exit early.
exists, isZombie, err := b.cfg.Graph.HasChannelEdge(
- edge.Version, edge.ChannelID,
+ ctx, edge.Version, edge.ChannelID,
)
if err != nil && !errors.Is(err, graphdb.ErrGraphNoEdgesFound) {
return fmt.Errorf("unable to check for edge existence: %w",
@@ -1340,7 +1340,7 @@ func (b *Builder) IsPublicNode(node route.Vertex) (bool, error) {
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) IsKnownEdge(chanID lnwire.ShortChannelID) bool {
exists, isZombie, _ := b.cfg.Graph.HasChannelEdge(
- lnwire.GossipVersion1, chanID.ToUint64(),
+ context.TODO(), lnwire.GossipVersion1, chanID.ToUint64(),
)
return exists || isZombie
@@ -1352,7 +1352,7 @@ func (b *Builder) IsKnownEdge(chanID lnwire.ShortChannelID) bool {
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) IsZombieEdge(chanID lnwire.ShortChannelID) (bool, error) {
_, isZombie, err := b.cfg.Graph.HasChannelEdge(
- lnwire.GossipVersion1, chanID.ToUint64(),
+ context.TODO(), lnwire.GossipVersion1, chanID.ToUint64(),
)
return isZombie, err
diff --git a/graph/builder_test.go b/graph/builder_test.go
index e561d25..2f9dc41 100644
--- a/graph/builder_test.go
+++ b/graph/builder_test.go
@@ -311,12 +311,12 @@ func TestWakeUpOnStaleBranch(t *testing.T) {
require.NoError(t, ctx.builder.AddEdge(ctxb, edge2))
// Check that the fundingTxs are in the graph db.
- has, isZombie, err := ctx.graph.HasChannelEdge(chanID1)
+ has, isZombie, err := ctx.graph.HasChannelEdge(t.Context(), chanID1)
require.NoError(t, err)
require.True(t, has)
require.False(t, isZombie)
- has, isZombie, err = ctx.graph.HasChannelEdge(chanID2)
+ has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID2)
require.NoError(t, err)
require.True(t, has)
require.False(t, isZombie)
@@ -363,12 +363,12 @@ func TestWakeUpOnStaleBranch(t *testing.T) {
// The channel with chanID2 should not be in the database anymore,
// since it is not confirmed on the longest chain. chanID1 should
// still be.
- has, isZombie, err = ctx.graph.HasChannelEdge(chanID1)
+ has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID1)
require.NoError(t, err)
require.True(t, has)
require.False(t, isZombie)
- has, isZombie, err = ctx.graph.HasChannelEdge(chanID2)
+ has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID2)
require.NoError(t, err)
require.False(t, has)
require.False(t, isZombie)
@@ -482,12 +482,12 @@ func TestDisconnectedBlocks(t *testing.T) {
require.NoError(t, ctx.builder.AddEdge(ctxb, edge2))
// Check that the fundingTxs are in the graph db.
- has, isZombie, err := ctx.graph.HasChannelEdge(chanID1)
+ has, isZombie, err := ctx.graph.HasChannelEdge(t.Context(), chanID1)
require.NoError(t, err)
require.True(t, has)
require.False(t, isZombie)
- has, isZombie, err = ctx.graph.HasChannelEdge(chanID2)
+ has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID2)
require.NoError(t, err)
require.True(t, has)
require.False(t, isZombie)
@@ -523,12 +523,12 @@ func TestDisconnectedBlocks(t *testing.T) {
// chanID2 should not be in the database anymore, since it is not
// confirmed on the longest chain. chanID1 should still be.
- has, isZombie, err = ctx.graph.HasChannelEdge(chanID1)
+ has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID1)
require.NoError(t, err)
require.True(t, has)
require.False(t, isZombie)
- has, isZombie, err = ctx.graph.HasChannelEdge(chanID2)
+ has, isZombie, err = ctx.graph.HasChannelEdge(t.Context(), chanID2)
require.NoError(t, err)
require.False(t, has)
require.False(t, isZombie)
@@ -594,7 +594,7 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) {
// The router should now be aware of the channel we created above.
hasChan, isZombie, err := ctx.graph.HasChannelEdge(
- chanID1.ToUint64(),
+ t.Context(), chanID1.ToUint64(),
)
require.NoError(t, err)
require.True(t, hasChan)
@@ -662,7 +662,7 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) {
// At this point, the channel that was pruned should no longer be known
// by the router.
hasChan, isZombie, err = ctx.graph.HasChannelEdge(
- chanID1.ToUint64(),
+ t.Context(), chanID1.ToUint64(),
)
require.NoError(t, err)
require.False(t, hasChan)
@@ -1611,7 +1611,7 @@ func assertChannelsPruned(t *testing.T, graph *graphdb.VersionedGraph,
for _, channel := range channels {
_, shouldPrune := pruned[channel.ChannelID]
exists, isZombie, err := graph.HasChannelEdge(
- channel.ChannelID,
+ t.Context(), channel.ChannelID,
)
require.NoError(t, err)
if shouldPrune {
diff --git a/graph/db/graph.go b/graph/db/graph.go
index 6c76f4d..1659ed9 100644
--- a/graph/db/graph.go
+++ b/graph/db/graph.go
@@ -681,10 +681,10 @@ func (c *ChannelGraph) HasV1ChannelEdge(ctx context.Context,
}
// HasChannelEdge returns true if the database knows of a channel edge.
-func (c *ChannelGraph) HasChannelEdge(v lnwire.GossipVersion,
- chanID uint64) (bool, bool, error) {
+func (c *ChannelGraph) HasChannelEdge(ctx context.Context,
+ v lnwire.GossipVersion, chanID uint64) (bool, bool, error) {
- return c.db.HasChannelEdge(v, chanID)
+ return c.db.HasChannelEdge(ctx, v, chanID)
}
// AddEdgeProof sets the proof of an existing edge in the graph database.
@@ -919,8 +919,10 @@ func (c *VersionedGraph) DeleteChannelEdges(ctx context.Context,
// passed channel ID and this graph's gossip version, and false otherwise. If it
// is not found, then the zombie index is checked and its result is returned as
// the second boolean.
-func (c *VersionedGraph) HasChannelEdge(chanID uint64) (bool, bool, error) {
- return c.db.HasChannelEdge(c.v, chanID)
+func (c *VersionedGraph) HasChannelEdge(ctx context.Context,
+ chanID uint64) (bool, bool, error) {
+
+ return c.db.HasChannelEdge(ctx, c.v, chanID)
}
// ForEachSourceNodeChannel iterates through all channels of the source node.
diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go
index 088b3dc..c223c10 100644
--- a/graph/db/graph_test.go
+++ b/graph/db/graph_test.go
@@ -918,13 +918,13 @@ func TestDisconnectBlockAtHeight(t *testing.T) {
// The two first edges should be removed from the db.
has, isZombie, err := graph.HasChannelEdge(
- lnwire.GossipVersion1, edgeInfo.ChannelID,
+ ctx, lnwire.GossipVersion1, edgeInfo.ChannelID,
)
require.NoError(t, err, "unable to query for edge")
require.False(t, has)
require.False(t, isZombie)
has, isZombie, err = graph.HasChannelEdge(
- lnwire.GossipVersion1, edgeInfo2.ChannelID,
+ ctx, lnwire.GossipVersion1, edgeInfo2.ChannelID,
)
require.NoError(t, err, "unable to query for edge")
require.False(t, has)
@@ -932,7 +932,7 @@ func TestDisconnectBlockAtHeight(t *testing.T) {
// Edge 3 should not be removed.
has, isZombie, err = graph.HasChannelEdge(
- lnwire.GossipVersion1, edgeInfo3.ChannelID,
+ ctx, lnwire.GossipVersion1, edgeInfo3.ChannelID,
)
require.NoError(t, err, "unable to query for edge")
require.True(t, has)
@@ -1184,7 +1184,7 @@ func testEdgeInfoUpdates(t *testing.T, v lnwire.GossipVersion) {
// Check for existence of the edge within the database, it should be
// found.
- found, isZombie, err := graph.HasChannelEdge(chanID)
+ found, isZombie, err := graph.HasChannelEdge(ctx, chanID)
require.NoError(t, err, "unable to query for edge")
require.True(t, found)
require.False(t, isZombie)
@@ -3360,7 +3360,7 @@ func TestStressTestChannelGraphAPI(t *testing.T) {
}
_, _, err := graph.HasChannelEdge(
- lnwire.GossipVersion1,
+ ctx, lnwire.GossipVersion1,
channel.id.ToUint64(),
)
diff --git a/graph/db/interfaces.go b/graph/db/interfaces.go
index 1ca68eb..a1dda31 100644
--- a/graph/db/interfaces.go
+++ b/graph/db/interfaces.go
@@ -218,8 +218,8 @@ type Store interface { //nolint:interfacebloat
// with the passed channel ID and gossip version, and false otherwise.
// If it is not found, then the zombie index is checked and its result
// is returned as the second boolean.
- HasChannelEdge(v lnwire.GossipVersion, chanID uint64) (bool, bool,
- error)
+ HasChannelEdge(ctx context.Context, v lnwire.GossipVersion,
+ chanID uint64) (bool, bool, error)
// DeleteChannelEdges removes edges with the given channel IDs from the
// database and marks them as zombies. This ensures that we're unable to
diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go
index d258361..4cfcedb 100644
--- a/graph/db/kv_store.go
+++ b/graph/db/kv_store.go
@@ -1427,16 +1427,14 @@ func (c *KVStore) HasV1ChannelEdge(_ context.Context,
// passed channel ID and gossip version, and false otherwise. If it is not
// found, then the zombie index is checked and its result is returned as the
// second boolean.
-func (c *KVStore) HasChannelEdge(v lnwire.GossipVersion,
+func (c *KVStore) HasChannelEdge(ctx context.Context, v lnwire.GossipVersion,
chanID uint64) (bool, bool, error) {
if v != lnwire.GossipVersion1 {
return false, false, ErrVersionNotSupportedForKVDB
}
- _, _, exists, isZombie, err := c.HasV1ChannelEdge(
- context.TODO(), chanID,
- )
+ _, _, exists, isZombie, err := c.HasV1ChannelEdge(ctx, chanID)
return exists, isZombie, err
}
diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go
index 5e358d3..388b25b 100644
--- a/graph/db/sql_store.go
+++ b/graph/db/sql_store.go
@@ -2361,8 +2361,8 @@ func (s *SQLStore) HasV1ChannelEdge(ctx context.Context,
// result is returned as the second boolean.
//
// NOTE: part of the Store interface.
-func (s *SQLStore) HasChannelEdge(v lnwire.GossipVersion,
- chanID uint64) (bool, bool, error) {
+func (s *SQLStore) HasChannelEdge(ctx context.Context,
+ v lnwire.GossipVersion, chanID uint64) (bool, bool, error) {
if !isKnownGossipVersion(v) {
return false, false, fmt.Errorf(
@@ -2370,8 +2370,6 @@ func (s *SQLStore) HasChannelEdge(v lnwire.GossipVersion,
)
}
- ctx := context.TODO()
-
var (
exists bool
isZombie bool
Why this scored 19/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.