graph/db: thread context through FetchChannelEdgesByID
What changed, and why it matters
This change simply passes a request-scoped cancellation signal (a 'context') through a database lookup function called FetchChannelEdgesByID. It does not fix a concrete bug by itself; it is plumbing work that lets future callers cancel long-running queries or propagate deadlines. There is no direct security vulnerability being patched here.
No immediate action required. Treat as routine refactoring. If context propagation is a goal, follow-up work should replace context.TODO() with meaningful contexts at call sites and implement cancellation in KVStore as well.
Security signals we found
No security-relevant behavioral change: the patch only changes function signatures to accept context.Context.
SQL store previously hard-coded context.TODO(); now it accepts an external context, but callers mostly still pass context.TODO().
KV store receives context but ignores it (underscore parameter), so no cancellation support is added there.
No input validation, authorization, cryptographic, or resource-limit changes are present.
No vendor disclosure or advisory text describes this as a security fix.
Evidence from the diff
The commit threads context.Context through the FetchChannelEdgesByID method across the ChannelGraph, VersionedGraph, KVStore, SQLStore, and all callers. The SQL implementation previously used context.TODO() internally and now accepts the caller’s context. The KV implementation accepts but ignores the context (named ‘_ context.Context’). Most production callers pass context.TODO(), so no immediate behavioral change occurs. This is a refactor to enable context propagation for cancellation/timeouts in future work.
Changed components
graph/db/graph.gograph/db/kv_store.gograph/db/sql_store.gograph/db/interfaces.gograph/db/notifications.godiscovery/chan_series.gograph/builder.golnrpc/invoicesrpc/addinvoice.golnrpc/invoicesrpc/interfaces.gorpcserver.goserver.goInspect captured patch +93 / −42
diff --git a/discovery/chan_series.go b/discovery/chan_series.go
index a1f0778..5fff3ca 100644
--- a/discovery/chan_series.go
+++ b/discovery/chan_series.go
@@ -354,7 +354,7 @@ func (c *ChanSeries) FetchChanUpdates(chain chainhash.Hash,
shortChanID lnwire.ShortChannelID) ([]*lnwire.ChannelUpdate1, error) {
chanInfo, e1, e2, err := c.graph.FetchChannelEdgesByID(
- shortChanID.ToUint64(),
+ context.TODO(), shortChanID.ToUint64(),
)
if err != nil {
return nil, err
diff --git a/graph/builder.go b/graph/builder.go
index 497b263..96b79fc 100644
--- a/graph/builder.go
+++ b/graph/builder.go
@@ -1259,7 +1259,9 @@ func (b *Builder) GetChannelByID(chanID lnwire.ShortChannelID) (
*models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, error) {
- return b.cfg.Graph.FetchChannelEdgesByID(chanID.ToUint64())
+ return b.cfg.Graph.FetchChannelEdgesByID(
+ context.TODO(), chanID.ToUint64(),
+ )
}
// FetchNode attempts to look up a target node by its identity public
diff --git a/graph/db/graph.go b/graph/db/graph.go
index e199ce0..a192ae3 100644
--- a/graph/db/graph.go
+++ b/graph/db/graph.go
@@ -739,12 +739,13 @@ func (c *ChannelGraph) FetchChannelEdgesByOutpoint(op *wire.OutPoint) (
}
// FetchChannelEdgesByID attempts to lookup directed edges by channel ID.
-func (c *ChannelGraph) FetchChannelEdgesByID(chanID uint64) (
+func (c *ChannelGraph) FetchChannelEdgesByID(ctx context.Context,
+ chanID uint64) (
*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, error) {
return c.db.FetchChannelEdgesByID(
- lnwire.GossipVersion1, chanID,
+ ctx, lnwire.GossipVersion1, chanID,
)
}
@@ -812,11 +813,12 @@ func (c *VersionedGraph) FetchNode(ctx context.Context,
}
// FetchChannelEdgesByID attempts to lookup directed edges by channel ID.
-func (c *VersionedGraph) FetchChannelEdgesByID(chanID uint64) (
+func (c *VersionedGraph) FetchChannelEdgesByID(ctx context.Context,
+ chanID uint64) (
*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, error) {
- return c.db.FetchChannelEdgesByID(c.v, chanID)
+ return c.db.FetchChannelEdgesByID(ctx, c.v, chanID)
}
// FetchChannelEdgesByOutpoint attempts to lookup directed edges by funding
diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go
index 8572808..b201e96 100644
--- a/graph/db/graph_test.go
+++ b/graph/db/graph_test.go
@@ -643,7 +643,7 @@ func testEdgeInsertionDeletion(t *testing.T, v lnwire.GossipVersion) {
// Ensure that both policies are returned as unknown (nil) and that
// the edge info round-trips correctly.
- dbEdge, e1, e2, err := graph.FetchChannelEdgesByID(chanID)
+ dbEdge, e1, e2, err := graph.FetchChannelEdgesByID(ctx, chanID)
require.NoError(t, err)
require.Nil(t, e1)
require.Nil(t, e2)
@@ -712,7 +712,7 @@ func testEdgeInsertionDeletion(t *testing.T, v lnwire.GossipVersion) {
// 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)
+ edge, _, _, err := graph.FetchChannelEdgesByID(ctx, chanID)
require.ErrorIs(t, err, ErrZombieEdge)
require.NotNil(t, edge)
@@ -1195,7 +1195,9 @@ func testEdgeInfoUpdates(t *testing.T, v lnwire.GossipVersion) {
// With the edges inserted, perform some queries to ensure that they've
// been inserted properly.
- dbEdgeInfo, dbEdge1, dbEdge2, err := graph.FetchChannelEdgesByID(chanID)
+ dbEdgeInfo, dbEdge1, dbEdge2, err := graph.FetchChannelEdgesByID(
+ ctx, chanID,
+ )
require.NoError(t, err, "unable to fetch channel by ID")
compareEdgePolicies(t, dbEdge1, edge1)
compareEdgePolicies(t, dbEdge2, edge2)
@@ -1578,7 +1580,9 @@ func testAddEdgeProof(t *testing.T, v lnwire.GossipVersion) {
require.NoError(t, graph.AddChannelEdge(ctx, edge1))
// Fetch the edge and assert that the proof is nil.
- dbEdge, _, _, err := graph.FetchChannelEdgesByID(edge1.ChannelID)
+ dbEdge, _, _, err := graph.FetchChannelEdgesByID(
+ ctx, edge1.ChannelID,
+ )
require.NoError(t, err)
require.Nil(t, dbEdge.AuthProof)
@@ -1608,7 +1612,9 @@ func testAddEdgeProof(t *testing.T, v lnwire.GossipVersion) {
require.NoError(t, graph.AddEdgeProof(scid1, proof))
// Fetch the edge again and assert that the proof is now set.
- dbEdge, _, _, err = graph.FetchChannelEdgesByID(edge1.ChannelID)
+ dbEdge, _, _, err = graph.FetchChannelEdgesByID(
+ ctx, edge1.ChannelID,
+ )
require.NoError(t, err)
require.NotNil(t, dbEdge.AuthProof)
@@ -1618,7 +1624,9 @@ func testAddEdgeProof(t *testing.T, v lnwire.GossipVersion) {
require.NoError(t, graph.AddChannelEdge(ctx, edge2))
// Fetch the edge and assert that the proof is set.
- dbEdge2, _, _, err := graph.FetchChannelEdgesByID(edge2.ChannelID)
+ dbEdge2, _, _, err := graph.FetchChannelEdgesByID(
+ ctx, edge2.ChannelID,
+ )
require.NoError(t, err)
require.NotNil(t, dbEdge2.AuthProof)
}
@@ -4486,7 +4494,9 @@ func TestEdgePolicyMissingMaxHTLC(t *testing.T) {
// we added is invalid according to the new format, it should be as we
// are not aware of the policy (indicated by the policy returned being
// nil)
- dbEdgeInfo, dbEdge1, dbEdge2, err := graph.FetchChannelEdgesByID(chanID)
+ dbEdgeInfo, dbEdge1, dbEdge2, err := graph.FetchChannelEdgesByID(
+ ctx, chanID,
+ )
require.NoError(t, err, "unable to fetch channel by ID")
// The first edge should have a nil-policy returned
@@ -4498,7 +4508,9 @@ func TestEdgePolicyMissingMaxHTLC(t *testing.T) {
// policies then become fully populated.
require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1))
- dbEdgeInfo, dbEdge1, dbEdge2, err = graph.FetchChannelEdgesByID(chanID)
+ dbEdgeInfo, dbEdge1, dbEdge2, err = graph.FetchChannelEdgesByID(
+ ctx, chanID,
+ )
require.NoError(t, err, "unable to fetch channel by ID")
compareEdgePolicies(t, dbEdge1, edge1)
compareEdgePolicies(t, dbEdge2, edge2)
diff --git a/graph/db/interfaces.go b/graph/db/interfaces.go
index a698183..550c78a 100644
--- a/graph/db/interfaces.go
+++ b/graph/db/interfaces.go
@@ -311,7 +311,8 @@ type Store interface { //nolint:interfacebloat
// zombie within the database. In this case, the ChannelEdgePolicy's
// will be nil, and the ChannelEdgeInfo will only include the public
// keys of each node.
- FetchChannelEdgesByID(v lnwire.GossipVersion, chanID uint64) (
+ FetchChannelEdgesByID(ctx context.Context, v lnwire.GossipVersion,
+ chanID uint64) (
*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, error)
diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go
index 09b180b..f77484a 100644
--- a/graph/db/kv_store.go
+++ b/graph/db/kv_store.go
@@ -3989,8 +3989,9 @@ func (c *KVStore) FetchChannelEdgesByOutpoint(v lnwire.GossipVersion,
// ErrZombieEdge an be returned if the edge is currently marked as a zombie
// within the database. In this case, the ChannelEdgePolicy's will be nil, and
// the ChannelEdgeInfo will only include the public keys of each node.
-func (c *KVStore) FetchChannelEdgesByID(v lnwire.GossipVersion,
- chanID uint64) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
+func (c *KVStore) FetchChannelEdgesByID(_ context.Context,
+ v lnwire.GossipVersion, chanID uint64) (
+ *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, error) {
if v != lnwire.GossipVersion1 {
diff --git a/graph/db/notifications.go b/graph/db/notifications.go
index 54a748c..6676cbe 100644
--- a/graph/db/notifications.go
+++ b/graph/db/notifications.go
@@ -1,6 +1,7 @@
package graphdb
import (
+ "context"
"errors"
"fmt"
"image/color"
@@ -412,7 +413,9 @@ func (c *ChannelGraph) addToTopologyChange(update *TopologyChange,
// We'll need to fetch the edge's information from the database
// in order to get the information concerning which nodes are
// being connected.
- edgeInfo, _, _, err := c.FetchChannelEdgesByID(m.ChannelID)
+ edgeInfo, _, _, err := c.FetchChannelEdgesByID(
+ context.TODO(), m.ChannelID,
+ )
if err != nil {
return fmt.Errorf("unable fetch channel edge: %w", err)
}
diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go
index a534ecc..9085e06 100644
--- a/graph/db/sql_store.go
+++ b/graph/db/sql_store.go
@@ -2050,12 +2050,12 @@ func (s *SQLStore) DeleteChannelEdges(ctx context.Context,
// the ChannelEdgeInfo will only include the public keys of each node.
//
// NOTE: part of the Store interface.
-func (s *SQLStore) FetchChannelEdgesByID(v lnwire.GossipVersion,
- chanID uint64) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
+func (s *SQLStore) FetchChannelEdgesByID(ctx context.Context,
+ v lnwire.GossipVersion, chanID uint64) (
+ *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, error) {
var (
- ctx = context.TODO()
edge *models.ChannelEdgeInfo
policy1, policy2 *models.ChannelEdgePolicy
chanIDB = channelIDToBytes(chanID)
diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go
index aba5da4..7d8ece0 100644
--- a/lnrpc/invoicesrpc/addinvoice.go
+++ b/lnrpc/invoicesrpc/addinvoice.go
@@ -522,8 +522,16 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
//nolint:ll
paths, err := blindedpath.BuildBlindedPaymentPaths(
&blindedpath.BuildBlindedPathCfg{
- FindRoutes: cfg.QueryBlindedRoutes,
- FetchChannelEdgesByID: cfg.Graph.FetchChannelEdgesByID,
+ FindRoutes: cfg.QueryBlindedRoutes,
+ FetchChannelEdgesByID: func(chanID uint64) (
+ *models.ChannelEdgeInfo,
+ *models.ChannelEdgePolicy,
+ *models.ChannelEdgePolicy, error) {
+
+ return cfg.Graph.FetchChannelEdgesByID(
+ context.TODO(), chanID,
+ )
+ },
FetchOurOpenChannels: cfg.ChanDB.FetchAllOpenChannels,
PathID: paymentAddr[:],
ValueMsat: invoice.Value,
@@ -790,9 +798,16 @@ func newSelectHopHintsCfg(invoicesCfg *AddInvoiceConfig,
FetchAllChannels: invoicesCfg.ChanDB.FetchAllChannels,
IsChannelActive: invoicesCfg.IsChannelActive,
IsPublicNode: invoicesCfg.Graph.IsPublicNode,
- FetchChannelEdgesByID: invoicesCfg.Graph.FetchChannelEdgesByID,
- GetAlias: invoicesCfg.GetAlias,
- MaxHopHints: maxHopHints,
+ FetchChannelEdgesByID: func(chanID uint64) (
+ *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
+ *models.ChannelEdgePolicy, error) {
+
+ return invoicesCfg.Graph.FetchChannelEdgesByID(
+ context.TODO(), chanID,
+ )
+ },
+ GetAlias: invoicesCfg.GetAlias,
+ MaxHopHints: maxHopHints,
}
}
diff --git a/lnrpc/invoicesrpc/interfaces.go b/lnrpc/invoicesrpc/interfaces.go
index df47d2f..d350af6 100644
--- a/lnrpc/invoicesrpc/interfaces.go
+++ b/lnrpc/invoicesrpc/interfaces.go
@@ -1,6 +1,8 @@
package invoicesrpc
import (
+ "context"
+
"github.com/lightningnetwork/lnd/graph/db/models"
)
@@ -9,8 +11,9 @@ type GraphSource interface {
// FetchChannelEdgesByID attempts to look up the two directed edges for
// the channel identified by the channel ID. If the channel can't be
// found, then graphdb.ErrEdgeNotFound is returned.
- FetchChannelEdgesByID(chanID uint64) (*models.ChannelEdgeInfo,
- *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error)
+ FetchChannelEdgesByID(ctx context.Context, chanID uint64) (
+ *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
+ *models.ChannelEdgePolicy, error)
// IsPublicNode is a helper method that determines whether the node with
// the given public key is seen as a public node in the graph from the
diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go
index 01f5d98..3dc1ddf 100644
--- a/routing/pathfind_test.go
+++ b/routing/pathfind_test.go
@@ -2199,7 +2199,9 @@ func runRouteFailMaxHTLC(t *testing.T, useCache bool) {
// Next, update the middle edge policy to only allow payments up to 100k
// msat.
graph := ctx.testGraphInstance.graph
- _, midEdge, _, err := graph.FetchChannelEdgesByID(firstToSecondID)
+ _, midEdge, _, err := graph.FetchChannelEdgesByID(
+ t.Context(), firstToSecondID,
+ )
require.NoError(t, err, "unable to fetch channel edges by ID")
midEdge.MessageFlags = 1
midEdge.MaxHTLC = payAmt - 1
@@ -2243,7 +2245,9 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) {
// path finding, as we don't consider the disable flag for local
// channels (and roasbeef is the source).
roasToPham := uint64(999991)
- _, e1, e2, err := graph.graph.FetchChannelEdgesByID(roasToPham)
+ _, e1, e2, err := graph.graph.FetchChannelEdgesByID(
+ t.Context(), roasToPham,
+ )
require.NoError(t, err, "unable to fetch edge")
e1.ChannelFlags |= lnwire.ChanUpdateDisabled
e1.LastUpdate = e1.LastUpdate.Add(time.Second)
@@ -2266,7 +2270,9 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) {
// Now, we'll modify the edge from phamnuwen -> sophon, to read that
// it's disabled.
phamToSophon := uint64(99999)
- _, e, _, err := graph.graph.FetchChannelEdgesByID(phamToSophon)
+ _, e, _, err := graph.graph.FetchChannelEdgesByID(
+ t.Context(), phamToSophon,
+ )
require.NoError(t, err, "unable to fetch edge")
e.ChannelFlags |= lnwire.ChanUpdateDisabled
e.LastUpdate = e.LastUpdate.Add(time.Second)
@@ -2349,7 +2355,9 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) {
// Finally, set the roasbeef->songoku bandwidth, but also set its
// disable flag.
bandwidths.hints[roasToSongoku] = 2 * payAmt
- _, e1, e2, err := graph.graph.FetchChannelEdgesByID(roasToSongoku)
+ _, e1, e2, err := graph.graph.FetchChannelEdgesByID(
+ t.Context(), roasToSongoku,
+ )
require.NoError(t, err, "unable to fetch edge")
e1.ChannelFlags |= lnwire.ChanUpdateDisabled
e1.LastUpdate = e1.LastUpdate.Add(time.Second)
diff --git a/routing/router_test.go b/routing/router_test.go
index 115c02c..d3dea61 100644
--- a/routing/router_test.go
+++ b/routing/router_test.go
@@ -454,7 +454,7 @@ func TestChannelUpdateValidation(t *testing.T) {
// Assert that the initially configured fee is retrieved correctly.
_, e1, e2, err := ctx.graph.FetchChannelEdgesByID(
- lnwire.NewShortChanIDFromInt(1).ToUint64(),
+ t.Context(), lnwire.NewShortChanIDFromInt(1).ToUint64(),
)
require.NoError(t, err, "cannot retrieve channel")
@@ -526,7 +526,7 @@ func TestChannelUpdateValidation(t *testing.T) {
require.Error(t, err, "expected route to fail with channel update")
_, e1, e2, err = ctx.graph.FetchChannelEdgesByID(
- lnwire.NewShortChanIDFromInt(1).ToUint64(),
+ t.Context(), lnwire.NewShortChanIDFromInt(1).ToUint64(),
)
require.NoError(t, err, "cannot retrieve channel")
@@ -548,7 +548,7 @@ func TestChannelUpdateValidation(t *testing.T) {
// This time a valid signature was supplied and the policy change should
// have been applied to the graph.
_, e1, e2, err = ctx.graph.FetchChannelEdgesByID(
- lnwire.NewShortChanIDFromInt(1).ToUint64(),
+ t.Context(), lnwire.NewShortChanIDFromInt(1).ToUint64(),
)
require.NoError(t, err, "cannot retrieve channel")
@@ -589,7 +589,7 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) {
// to sophon. We'll obtain this as we'll need to to generate the
// FeeInsufficient error that we'll send back.
_, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID(
- songokuSophonChanID,
+ t.Context(), songokuSophonChanID,
)
require.NoError(t, err, "unable to fetch chan id")
@@ -936,7 +936,9 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
chanID := ctx.getChannelIDFromAlias(t, "roasbeef", "songoku")
roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID)
- _, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID(chanID)
+ _, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID(
+ t.Context(), chanID,
+ )
require.NoError(t, err, "unable to fetch chan id")
errChanUpdate := lnwire.ChannelUpdate1{
diff --git a/rpcserver.go b/rpcserver.go
index bfe537a..4a9c8e7 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -716,7 +716,9 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server,
FetchChannelCapacity: func(chanID uint64) (btcutil.Amount,
error) {
- info, _, _, err := graph.FetchChannelEdgesByID(chanID)
+ info, _, _, err := graph.FetchChannelEdgesByID(
+ ctx, chanID,
+ )
if err != nil {
return 0, err
}
@@ -734,7 +736,7 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server,
route.Vertex, error) {
info, _, _, err := graph.FetchChannelEdgesByID(
- chanID,
+ ctx, chanID,
)
if err != nil {
return route.Vertex{}, route.Vertex{},
@@ -7106,7 +7108,7 @@ func (r *rpcServer) GetNodeMetrics(ctx context.Context,
// uniquely identify the location of transaction's funding output within the
// blockchain. The former is an 8-byte integer, while the latter is a string
// formatted as funding_txid:output_index.
-func (r *rpcServer) GetChanInfo(_ context.Context,
+func (r *rpcServer) GetChanInfo(ctx context.Context,
in *lnrpc.ChanInfoRequest) (*lnrpc.ChannelEdge, error) {
graph := r.server.graphDB
@@ -7120,7 +7122,7 @@ func (r *rpcServer) GetChanInfo(_ context.Context,
switch {
case in.ChanId != 0:
edgeInfo, edge1, edge2, err = graph.FetchChannelEdgesByID(
- in.ChanId,
+ ctx, in.ChanId,
)
case in.ChanPoint != "":
diff --git a/server.go b/server.go
index 248a69c..6c68971 100644
--- a/server.go
+++ b/server.go
@@ -1397,7 +1397,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
*models.ChannelEdgePolicy, error) {
info, e1, e2, err := s.graphDB.FetchChannelEdgesByID(
- scid.ToUint64(),
+ context.TODO(), scid.ToUint64(),
)
if errors.Is(err, graphdb.ErrEdgeNotFound) {
// This is unlikely but there is a slim chance of this
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.