multi: add DeleteChannelEdges to VersionedGraph
What changed, and why it matters
This commit refactors how LND deletes channel edges from the network graph so that deletions go through a new version-aware wrapper. The change is mostly a code cleanup to support multiple gossip graph versions, but it includes a TODO noting that one RPC path still only supports v1 channel deletions. There is no direct evidence in the commit of an exploitable security bug.
Treat as routine maintenance. Reviewers should verify that all DeleteChannelEdges callers use the correct graph version and that the v2 TODO is tracked. No immediate security patch is indicated by the diff alone.
Security signals we found
Refactoring of channel-deletion code paths to be gossip-version aware
Introduction of a TODO indicating incomplete support for v2 channel deletion in the abandon-channel RPC
Cache invalidation added in the new wrapper to keep in-memory graph state consistent with DB deletions
Evidence from the diff
The patch adds DeleteChannelEdges to graph/db.VersionedGraph and updates three call sites (graph builder zombie pruning, RPC abandon-channel, and server channel removal) to use the versioned v1Graph instead of the raw graphDB. The wrapper forwards to the underlying DB with a gossip version parameter and invalidates the graph cache. A TODO in rpcserver.go explicitly states v2 channel deletions are not yet supported. The change appears to be architectural groundwork for gossip v2 rather than a fix for a known vulnerability.
Changed components
graph/db/graph.gograph/builder.gorpcserver.goserver.goInspect captured patch +36 / −4
diff --git a/graph/builder.go b/graph/builder.go
index 3e15713..96a7582 100644
--- a/graph/builder.go
+++ b/graph/builder.go
@@ -625,7 +625,7 @@ func (b *Builder) pruneZombieChans() error {
toPrune = append(toPrune, chanID)
log.Tracef("Pruning zombie channel with ChannelID(%v)", chanID)
}
- err := b.cfg.Graph.DeleteChannelEdges(
+ err := b.v1Graph.DeleteChannelEdges(
b.cfg.StrictZombiePruning, true, toPrune...,
)
if err != nil {
diff --git a/graph/db/graph.go b/graph/db/graph.go
index f2a5ffb..302813f 100644
--- a/graph/db/graph.go
+++ b/graph/db/graph.go
@@ -831,6 +831,36 @@ func (c *VersionedGraph) SourceNode(ctx context.Context) (*models.Node,
return c.db.SourceNode(ctx, c.v)
}
+// DeleteChannelEdges removes edges with the given channel IDs from the
+// database and marks them as zombies. This ensures that we're unable to re-add
+// it to our database once again. If an edge does not exist within the
+// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is
+// true, then when we mark these edges as zombies, we'll set up the keys such
+// that we require the node that failed to send the fresh update to be the one
+// that resurrects the channel from its zombie state. The markZombie bool
+// denotes whether to mark the channel as a zombie.
+func (c *VersionedGraph) DeleteChannelEdges(strictZombiePruning,
+ markZombie bool, chanIDs ...uint64) error {
+
+ infos, err := c.db.DeleteChannelEdges(
+ c.v, strictZombiePruning, markZombie, chanIDs...,
+ )
+ if err != nil {
+ return err
+ }
+
+ if c.graphCache != nil {
+ for _, info := range infos {
+ c.graphCache.RemoveChannel(
+ info.NodeKey1Bytes, info.NodeKey2Bytes,
+ info.ChannelID,
+ )
+ }
+ }
+
+ return err
+}
+
// MakeTestGraph creates a new instance of the ChannelGraph for testing
// purposes. The backing Store implementation depends on the version of
// NewTestDB included in the current build.
diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go
index 6584e12..ed6a5f5 100644
--- a/graph/db/graph_test.go
+++ b/graph/db/graph_test.go
@@ -153,6 +153,7 @@ var versionedTests = []versionedTest{
func TestVersionedDBs(t *testing.T) {
t.Parallel()
+ // Run all v1 tests.
for _, vt := range versionedTests {
vt := vt
diff --git a/rpcserver.go b/rpcserver.go
index 0c49639..927635a 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -3196,7 +3196,7 @@ func createRPCCloseUpdate(
// abandonChanFromGraph attempts to remove a channel from the channel graph. If
// we can't find the chanID in the graph, then we assume it has already been
// removed, and will return a nop.
-func abandonChanFromGraph(chanGraph *graphdb.ChannelGraph,
+func abandonChanFromGraph(chanGraph *graphdb.VersionedGraph,
chanPoint *wire.OutPoint) error {
// First, we'll obtain the channel ID. If we can't locate this, then
@@ -3237,7 +3237,8 @@ func (r *rpcServer) abandonChan(chanPoint *wire.OutPoint,
if err != nil {
return err
}
- err = abandonChanFromGraph(r.server.graphDB, chanPoint)
+ // TODO: update to support deletions for v2 channels.
+ err = abandonChanFromGraph(r.server.v1Graph, chanPoint)
if err != nil {
return err
}
diff --git a/server.go b/server.go
index 78b1971..7ba7417 100644
--- a/server.go
+++ b/server.go
@@ -1422,7 +1422,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
return nil, fmt.Errorf("we don't have an edge")
}
- err = s.graphDB.DeleteChannelEdges(
+ err = s.v1Graph.DeleteChannelEdges(
false, false, scid.ToUint64(),
)
return ourPolicy, err
Why this scored 26/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.