graph/db: thread context through ForEachNodeDirectedChannel
What changed, and why it matters
This change is a routine code cleanup: it threads a request-scoped 'context' parameter through a graph traversal function called ForEachNodeDirectedChannel so that database operations can be cancelled or time out properly. It does not fix a security bug and introduces no user-visible behavior change.
No security action required. Treat as normal refactoring; consider future work to propagate real contexts instead of context.TODO() in routing callers to improve cancellation/timeout handling.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors the NodeTraverser/Store/Graph interfaces and all implementations (KVStore, SQLStore, ChannelGraph, mockGraph) so that ForEachNodeDirectedChannel accepts a context.Context as its first argument. The SQL implementation replaces context.TODO() with the passed-in context, while the KV implementation ignores it with ‘_’. Callers in routing (bandwidth.go, pathfind.go, unified_edges.go) pass context.TODO() because they currently lack a propagated context. This is a plumbing/refactoring change with no functional or security impact on its own.
Changed components
graph/db/graph.gograph/db/interfaces.gograph/db/kv_store.gograph/db/sql_store.gorouting/graph.gorouting/bandwidth.gorouting/pathfind.gorouting/unified_edges.goInspect captured patch +36 / −31
diff --git a/graph/db/graph.go b/graph/db/graph.go
index 1173c7a..51284d1 100644
--- a/graph/db/graph.go
+++ b/graph/db/graph.go
@@ -217,8 +217,9 @@ func (c *ChannelGraph) populateCache(ctx context.Context) error {
// Unknown policies are passed into the callback as nil values.
//
// NOTE: this is part of the graphdb.NodeTraverser interface.
-func (c *ChannelGraph) ForEachNodeDirectedChannel(node route.Vertex,
- cb func(channel *DirectedChannel) error, reset func()) error {
+func (c *ChannelGraph) 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)
@@ -227,7 +228,9 @@ func (c *ChannelGraph) ForEachNodeDirectedChannel(node route.Vertex,
// TODO(elle): once the no-cache path needs to support
// pathfinding across gossip versions, this should iterate
// across all versions rather than defaulting to v1.
- return c.db.ForEachNodeDirectedChannel(gossipV1, node, cb, reset)
+ return c.db.ForEachNodeDirectedChannel(
+ ctx, gossipV1, node, cb, reset,
+ )
}
// FetchNodeFeatures returns the features of the given node. If no features are
diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go
index 7a719a2..097e182 100644
--- a/graph/db/graph_test.go
+++ b/graph/db/graph_test.go
@@ -1875,7 +1875,7 @@ func testGraphTraversalCacheable(t *testing.T, v lnwire.GossipVersion) {
// Query the ChannelGraph which uses the cache to iterate
// through the channels for each node.
err = graph.ChannelGraph.ForEachNodeDirectedChannel(
- node, func(d *DirectedChannel) error {
+ ctx, node, func(d *DirectedChannel) error {
delete(chanIndex, d.ChannelID)
return nil
}, func() {},
@@ -1884,7 +1884,7 @@ func testGraphTraversalCacheable(t *testing.T, v lnwire.GossipVersion) {
// Now skip the cache and query the DB directly.
err = graph.db.ForEachNodeDirectedChannel(
- v, node, func(d *DirectedChannel) error {
+ ctx, v, node, func(d *DirectedChannel) error {
delete(chanIndex2, d.ChannelID)
return nil
}, func() {},
@@ -4932,7 +4932,7 @@ func testGraphCacheForEachNodeChannel(t *testing.T,
getSingleChannel := func() *DirectedChannel {
var ch *DirectedChannel
err := graph.db.ForEachNodeDirectedChannel(
- v, node1.PubKeyBytes,
+ ctx, v, node1.PubKeyBytes,
func(c *DirectedChannel) error {
require.Nil(t, ch)
ch = c
diff --git a/graph/db/interfaces.go b/graph/db/interfaces.go
index 4983c9c..568145c 100644
--- a/graph/db/interfaces.go
+++ b/graph/db/interfaces.go
@@ -21,7 +21,7 @@ import (
type NodeTraverser interface {
// ForEachNodeDirectedChannel calls the callback for every channel of
// the given node.
- ForEachNodeDirectedChannel(nodePub route.Vertex,
+ ForEachNodeDirectedChannel(ctx context.Context, nodePub route.Vertex,
cb func(channel *DirectedChannel) error, reset func()) error
// FetchNodeFeatures returns the features of the given node.
@@ -34,8 +34,9 @@ type NodeTraverser interface {
type Store interface { //nolint:interfacebloat
// ForEachNodeDirectedChannel calls the callback for every channel of
// the given node.
- ForEachNodeDirectedChannel(v lnwire.GossipVersion, nodePub route.Vertex,
- cb func(channel *DirectedChannel) error, reset func()) error
+ ForEachNodeDirectedChannel(ctx context.Context, v lnwire.GossipVersion,
+ nodePub route.Vertex, cb func(channel *DirectedChannel) error,
+ reset func()) error
// FetchNodeFeatures returns the features of the given node.
FetchNodeFeatures(ctx context.Context, v lnwire.GossipVersion,
diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go
index 5a27ee9..2e3496a 100644
--- a/graph/db/kv_store.go
+++ b/graph/db/kv_store.go
@@ -669,9 +669,9 @@ func (c *KVStore) fetchNodeFeatures(tx kvdb.RTx,
// Unknown policies are passed into the callback as nil values.
//
// NOTE: this is part of the graphdb.NodeTraverser interface.
-func (c *KVStore) ForEachNodeDirectedChannel(v lnwire.GossipVersion,
- nodePub route.Vertex, cb func(channel *DirectedChannel) error,
- reset func()) error {
+func (c *KVStore) ForEachNodeDirectedChannel(_ context.Context,
+ v lnwire.GossipVersion, nodePub route.Vertex,
+ cb func(channel *DirectedChannel) error, reset func()) error {
if v != lnwire.GossipVersion1 {
return ErrVersionNotSupportedForKVDB
@@ -4501,8 +4501,8 @@ type nodeTraverserSession struct {
//
// NOTE: Part of the NodeTraverser interface.
func (c *nodeTraverserSession) ForEachNodeDirectedChannel(
- nodePub route.Vertex, cb func(channel *DirectedChannel) error,
- _ func()) error {
+ _ context.Context, nodePub route.Vertex,
+ cb func(channel *DirectedChannel) error, _ func()) error {
return c.db.forEachNodeDirectedChannel(c.tx, nodePub, cb, func() {})
}
diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go
index 0235165..e195fc1 100644
--- a/graph/db/sql_store.go
+++ b/graph/db/sql_store.go
@@ -1006,11 +1006,9 @@ func (s *SQLStore) ForEachNode(ctx context.Context,
// Unknown policies are passed into the callback as nil values.
//
// NOTE: this is part of the graphdb.NodeTraverser interface.
-func (s *SQLStore) ForEachNodeDirectedChannel(v lnwire.GossipVersion,
- nodePub route.Vertex, cb func(channel *DirectedChannel) error,
- reset func()) error {
-
- var ctx = context.TODO()
+func (s *SQLStore) ForEachNodeDirectedChannel(ctx context.Context,
+ v lnwire.GossipVersion, nodePub route.Vertex,
+ cb func(channel *DirectedChannel) error, reset func()) error {
return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error {
return forEachNodeDirectedChannel(ctx, db, v, nodePub, cb)
@@ -3375,10 +3373,8 @@ func newSQLNodeTraverser(db SQLQueries,
//
// NOTE: Part of the NodeTraverser interface.
func (s *sqlNodeTraverser) ForEachNodeDirectedChannel(
- nodePub route.Vertex, cb func(channel *DirectedChannel) error,
- _ func()) error {
-
- ctx := context.TODO()
+ ctx context.Context, nodePub route.Vertex,
+ cb func(channel *DirectedChannel) error, _ func()) error {
return forEachNodeDirectedChannel(
ctx, s.db, lnwire.GossipVersion1, nodePub, cb,
diff --git a/routing/bandwidth.go b/routing/bandwidth.go
index df68cea..32bc45e 100644
--- a/routing/bandwidth.go
+++ b/routing/bandwidth.go
@@ -1,6 +1,7 @@
package routing
import (
+ "context"
"fmt"
"github.com/lightningnetwork/lnd/fn/v2"
@@ -64,7 +65,8 @@ func newBandwidthManager(graph Graph, sourceNode route.Vertex,
// First, we'll collect the set of outbound edges from the target
// source node and add them to our bandwidth manager's map of channels.
err := graph.ForEachNodeDirectedChannel(
- sourceNode, func(channel *graphdb.DirectedChannel) error {
+ context.TODO(), sourceNode,
+ func(channel *graphdb.DirectedChannel) error {
shortID := lnwire.NewShortChanIDFromInt(
channel.ChannelID,
)
diff --git a/routing/graph.go b/routing/graph.go
index 2cfaec0..73ca334 100644
--- a/routing/graph.go
+++ b/routing/graph.go
@@ -15,7 +15,7 @@ import (
type Graph interface {
// ForEachNodeDirectedChannel calls the callback for every channel of
// the given node.
- ForEachNodeDirectedChannel(nodePub route.Vertex,
+ ForEachNodeDirectedChannel(ctx context.Context, nodePub route.Vertex,
cb func(channel *graphdb.DirectedChannel) error,
reset func()) error
diff --git a/routing/mock_graph_test.go b/routing/mock_graph_test.go
index b11bdf8..0f657bd 100644
--- a/routing/mock_graph_test.go
+++ b/routing/mock_graph_test.go
@@ -166,8 +166,9 @@ func (m *mockGraph) addChannel(id uint64, node1id, node2id byte,
// forEachNodeChannel calls the callback for every channel of the given node.
//
// NOTE: Part of the Graph interface.
-func (m *mockGraph) ForEachNodeDirectedChannel(nodePub route.Vertex,
- cb func(channel *graphdb.DirectedChannel) error, _ func()) error {
+func (m *mockGraph) ForEachNodeDirectedChannel(_ context.Context,
+ nodePub route.Vertex, cb func(channel *graphdb.DirectedChannel) error,
+ _ func()) error {
// Look up the mock node.
node, ok := m.nodes[nodePub]
diff --git a/routing/pathfind.go b/routing/pathfind.go
index 0f177c3..97ce809 100644
--- a/routing/pathfind.go
+++ b/routing/pathfind.go
@@ -578,7 +578,7 @@ func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
// Iterate over all channels of the to node.
err := g.ForEachNodeDirectedChannel(
- node, cb, func() {
+ context.TODO(), node, cb, func() {
max = 0
total = 0
},
@@ -1291,7 +1291,7 @@ func findBlindedPaths(g Graph, target route.Vertex,
nextTargetReset = nextTarget
)
err := g.ForEachNodeDirectedChannel(
- nextTarget,
+ context.TODO(), nextTarget,
func(channel *graphdb.DirectedChannel) error {
// This is not the right channel, continue to
// the node's other channels.
@@ -1466,7 +1466,8 @@ func processNodeForBlindedPath(g Graph, node route.Vertex,
// Now, iterate over the node's channels in search for paths to this
// node that can be used for blinded paths
err = g.ForEachNodeDirectedChannel(
- node, func(channel *graphdb.DirectedChannel) error {
+ context.TODO(), node,
+ func(channel *graphdb.DirectedChannel) error {
// Keep track of how many incoming channels this node
// has. We only use a node as an introduction node if it
// has channels other than the one that lead us to it.
diff --git a/routing/unified_edges.go b/routing/unified_edges.go
index fda06aa..7966f67 100644
--- a/routing/unified_edges.go
+++ b/routing/unified_edges.go
@@ -1,6 +1,7 @@
package routing
import (
+ "context"
"math"
"github.com/btcsuite/btcd/btcutil"
@@ -116,7 +117,7 @@ func (u *nodeEdgeUnifier) addGraphPolicies(g Graph) error {
// Iterate over all channels of the to node.
err := g.ForEachNodeDirectedChannel(
- u.toNode, cb, func() {
+ context.TODO(), u.toNode, cb, func() {
channels = nil
},
)
Why this scored 15/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.