graph/db: version DeleteChannelEdges, IsPublicNode, IsZombieEdge
What changed, and why it matters
This commit is a code-cleanup change that makes several Lightning Network graph database methods aware of which 'gossip version' they are working with, instead of always assuming version 1. It also fixes two places where the wrong graph object was being passed to invoice-related code. The change is mostly architectural and does not by itself fix a known, exploitable bug, but it removes a class of mistakes where code could accidentally operate on the wrong version of the graph data.
Review remaining ChannelGraph and Store methods for any other hard-coded GossipVersion1 assumptions; ensure all production call sites pass the correct gossip version rather than defaulting to version 1. Treat this as preventive maintenance, not an urgent security patch.
Security signals we found
Hard-coded gossip version removed from graph DB public API
Call sites corrected to use version-aware graph wrapper
Potential for version mismatch bugs between gossip versions reduced
No explicit vulnerability, exploit primitive, or boundary violation shown in diff
Evidence from the diff
The patch propagates lnwire.GossipVersion through ChannelGraph.DeleteChannelEdges, IsPublicNode, and IsZombieEdge, forwarding it to the underlying Store rather than hard-coding GossipVersion1. It updates tests and two RPC call sites: rpcserver.go AddInvoice now uses s.server.v1Graph (a *VersionedGraph), and subrpcserver_config.go wraps graphDB in NewVersionedGraph with GossipVersion1 when populating invoicesrpc config via reflection. This aligns callers with the version-aware invoicesrpc.GraphSource interface.
Changed components
graph/db/graph.gograph/db/graph_test.gorpcserver.gosubrpcserver_config.goChannelGraph APIinvoicesrpc.GraphSource interface consumersInspect captured patch +37 / −20
diff --git a/graph/db/graph.go b/graph/db/graph.go
index be0ac82..01683ff 100644
--- a/graph/db/graph.go
+++ b/graph/db/graph.go
@@ -395,11 +395,11 @@ func (c *ChannelGraph) MarkEdgeLive(ctx context.Context,
// that resurrects the channel from its zombie state. The markZombie bool
// denotes whether to mark the channel as a zombie.
func (c *ChannelGraph) DeleteChannelEdges(ctx context.Context,
- strictZombiePruning, markZombie bool, chanIDs ...uint64) error {
+ v lnwire.GossipVersion, strictZombiePruning, markZombie bool,
+ chanIDs ...uint64) error {
infos, err := c.db.DeleteChannelEdges(
- ctx, lnwire.GossipVersion1, strictZombiePruning, markZombie,
- chanIDs...,
+ ctx, v, strictZombiePruning, markZombie, chanIDs...,
)
if err != nil {
return err
@@ -656,11 +656,12 @@ func (c *ChannelGraph) HasV1Node(ctx context.Context,
return c.db.HasV1Node(ctx, nodePub)
}
-// IsPublicNode determines whether the node is seen as public in the graph.
+// IsPublicNode determines whether the node is seen as public in the graph for
+// the given gossip version.
func (c *ChannelGraph) IsPublicNode(ctx context.Context,
- pubKey [33]byte) (bool, error) {
+ v lnwire.GossipVersion, pubKey [33]byte) (bool, error) {
- return c.db.IsPublicNode(ctx, lnwire.GossipVersion1, pubKey)
+ return c.db.IsPublicNode(ctx, v, pubKey)
}
// ForEachChannel iterates through all channel edges stored within the graph.
@@ -774,11 +775,13 @@ func (c *ChannelGraph) ChannelView(ctx context.Context) ([]EdgePoint, error) {
return c.db.ChannelView(ctx)
}
-// IsZombieEdge returns whether the edge is considered zombie.
+// IsZombieEdge returns whether the edge is considered zombie for the given
+// gossip version.
func (c *ChannelGraph) IsZombieEdge(ctx context.Context,
- chanID uint64) (bool, [33]byte, [33]byte, error) {
+ v lnwire.GossipVersion, chanID uint64) (bool, [33]byte, [33]byte,
+ error) {
- return c.db.IsZombieEdge(ctx, lnwire.GossipVersion1, chanID)
+ return c.db.IsZombieEdge(ctx, v, chanID)
}
// NumZombies returns the current number of zombie channels in the graph.
diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go
index a7bc4d3..b455d8b 100644
--- a/graph/db/graph_test.go
+++ b/graph/db/graph_test.go
@@ -2955,7 +2955,7 @@ func TestFilterKnownChanIDsZombieRevival(t *testing.T) {
isZombie := func(scid lnwire.ShortChannelID) bool {
zombie, _, _, err := graph.IsZombieEdge(
- ctx, scid.ToUint64(),
+ ctx, lnwire.GossipVersion1, scid.ToUint64(),
)
require.NoError(t, err)
@@ -3082,7 +3082,8 @@ func TestFilterKnownChanIDs(t *testing.T) {
)
require.NoError(t, graph.AddChannelEdge(ctx, channel))
err := graph.DeleteChannelEdges(
- ctx, false, true, channel.ChannelID,
+ ctx, lnwire.GossipVersion1, false, true,
+ channel.ChannelID,
)
require.NoError(t, err)
@@ -3423,7 +3424,8 @@ func TestStressTestChannelGraphAPI(t *testing.T) {
}
err := graph.DeleteChannelEdges(
- ctx, strictPruning, markZombie,
+ ctx, lnwire.GossipVersion1,
+ strictPruning, markZombie,
chanIDs...,
)
if err != nil &&
@@ -4384,7 +4386,9 @@ func BenchmarkIsPublicNode(b *testing.B) {
// Query random nodes to avoid query caching and better
// represent real-world query patterns.
nodePub := nodes[rng.Intn(len(nodes))].PubKeyBytes
- _, err := graph.IsPublicNode(b.Context(), nodePub)
+ _, err := graph.IsPublicNode(
+ b.Context(), lnwire.GossipVersion1, nodePub,
+ )
require.NoError(b, err)
}
}
@@ -4615,17 +4619,21 @@ func TestGraphZombieIndex(t *testing.T) {
// Since the edge is known the graph and it isn't a zombie, IsZombieEdge
// should not report the channel as a zombie.
- isZombie, _, _, err := graph.IsZombieEdge(ctx, edge.ChannelID)
+ isZombie, _, _, err := graph.IsZombieEdge(
+ ctx, lnwire.GossipVersion1, edge.ChannelID,
+ )
require.NoError(t, err)
require.False(t, isZombie)
assertNumZombies(t, graph, 0)
// If we delete the edge and mark it as a zombie, then we should expect
// to see it within the index.
- err = graph.DeleteChannelEdges(ctx, false, true, edge.ChannelID)
+ err = graph.DeleteChannelEdges(
+ ctx, lnwire.GossipVersion1, false, true, edge.ChannelID,
+ )
require.NoError(t, err, "unable to mark edge as zombie")
isZombie, pubKey1, pubKey2, err := graph.IsZombieEdge(
- ctx, edge.ChannelID,
+ ctx, lnwire.GossipVersion1, edge.ChannelID,
)
require.NoError(t, err)
require.True(t, isZombie)
@@ -4647,7 +4655,9 @@ func TestGraphZombieIndex(t *testing.T) {
ErrZombieEdgeNotFound,
)
- isZombie, _, _, err = graph.IsZombieEdge(ctx, edge.ChannelID)
+ isZombie, _, _, err = graph.IsZombieEdge(
+ ctx, lnwire.GossipVersion1, edge.ChannelID,
+ )
require.NoError(t, err)
require.False(t, isZombie)
@@ -4661,7 +4671,9 @@ func TestGraphZombieIndex(t *testing.T) {
)
require.NoError(t, err, "unable to mark edge as zombie")
- isZombie, _, _, err = graph.IsZombieEdge(ctx, edge.ChannelID)
+ isZombie, _, _, err = graph.IsZombieEdge(
+ ctx, lnwire.GossipVersion1, edge.ChannelID,
+ )
require.NoError(t, err)
require.True(t, isZombie)
assertNumZombies(t, graph, 1)
diff --git a/rpcserver.go b/rpcserver.go
index 401f33d..ac435fd 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -6462,7 +6462,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
NodeSigner: r.server.nodeSigner,
DefaultCLTVExpiry: defaultDelta,
ChanDB: r.server.chanStateDB,
- Graph: r.server.graphDB,
+ Graph: r.server.v1Graph,
GenInvoiceFeatures: func() *lnwire.FeatureVector {
v := r.server.featureMgr.Get(feature.SetInvoice)
diff --git a/subrpcserver_config.go b/subrpcserver_config.go
index d55d5a4..8b3641d 100644
--- a/subrpcserver_config.go
+++ b/subrpcserver_config.go
@@ -265,7 +265,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config,
reflect.ValueOf(defaultDelta),
)
subCfgValue.FieldByName("Graph").Set(
- reflect.ValueOf(graphDB),
+ reflect.ValueOf(graphdb.NewVersionedGraph(
+ graphDB, lnwire.GossipVersion1,
+ )),
)
subCfgValue.FieldByName("ChanStateDB").Set(
reflect.ValueOf(chanStateDB),
Why this scored 27/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.