graph/db: add version parameter to DeleteChannelEdges
What changed, and why it matters
This commit changes how LND deletes Lightning channel records from its graph database. It adds a 'version' parameter so the code can distinguish between older (v1) and newer (v2) channel announcement formats. Right now it mostly hard-codes v1 and explicitly rejects or errors on v2 in some paths, so it is a preparatory/refactoring change rather than a finished security fix. There is no evidence in the commit that this resolves an active vulnerability or that it was released as a security patch.
Treat as a normal refactoring/infrastructure commit. Monitor follow-up commits that complete v2 strict zombie pruning and remove the TODO. If this commit appears in a release note as a security fix, request the associated CVE or advisory; otherwise no immediate patching urgency is indicated by the diff alone.
Security signals we found
New version-gating added to channel-edge deletion paths
KV store explicitly rejects unsupported gossip versions
SQL store propagates version into channel lookup queries
Strict zombie pruning explicitly errors on v2 with a TODO
No direct bug fix, vulnerability description, or CVE reference in commit
Evidence from the diff
The patch threads a lnwire.GossipVersion argument through DeleteChannelEdges, the Store interface, and the KV/SQL store implementations. KVStore rejects any non-v1 version with ErrVersionNotSupportedForKVDB. SQLStore passes the version into forEachChanWithPoliciesInSCIDList and handleZombieMarking, but handleZombieMarking returns an error for strict zombie pruning when the version is not v1, with a TODO to support v2 last-update times. FetchChanInfos is hard-coded to v1. The change is defensive scaffolding for future v2 channel handling; it does not by itself fix a known bug or close an attack vector.
Changed components
graph/db/graph.gograph/db/interfaces.gograph/db/kv_store.gograph/db/sql_store.gograph/db/graph_test.goInspect captured patch +43 / −18
diff --git a/graph/db/graph.go b/graph/db/graph.go
index 3fbb00e..f2a5ffb 100644
--- a/graph/db/graph.go
+++ b/graph/db/graph.go
@@ -371,7 +371,8 @@ func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool,
chanIDs ...uint64) error {
infos, err := c.db.DeleteChannelEdges(
- strictZombiePruning, markZombie, chanIDs...,
+ lnwire.GossipVersion1, strictZombiePruning, markZombie,
+ chanIDs...,
)
if err != nil {
return err
diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go
index d720326..d819582 100644
--- a/graph/db/graph_test.go
+++ b/graph/db/graph_test.go
@@ -713,7 +713,9 @@ func createEdge(version lnwire.GossipVersion, height, txIndex uint32,
// Create a test funding script.
fundingScript := []byte{0x00, 0x20}
- fundingScript = append(fundingScript, bytes.Repeat([]byte{0xbb}, 32)...)
+ fundingScript = append(
+ fundingScript, bytes.Repeat([]byte{0xbb}, 32)...,
+ )
proof := models.NewV2ChannelAuthProof(testSig.Serialize())
@@ -777,7 +779,9 @@ func TestDisconnectBlockAtHeight(t *testing.T) {
// Create an edge which has its block height at 156.
height := uint32(156)
- edgeInfo, _ := createEdge(lnwire.GossipVersion1, height, 0, 0, 0, node1, node2)
+ edgeInfo, _ := createEdge(
+ lnwire.GossipVersion1, height, 0, 0, 0, node1, node2,
+ )
// Create an edge with block height 157. We give it
// maximum values for tx index and position, to make
@@ -2238,8 +2242,12 @@ func TestHighestChanID(t *testing.T) {
// The first channel with be at height 10, while the other will be at
// height 100.
- edge1, _ := createEdge(lnwire.GossipVersion1, 10, 0, 0, 0, node1, node2)
- edge2, chanID2 := createEdge(lnwire.GossipVersion1, 100, 0, 0, 0, node1, node2)
+ edge1, _ := createEdge(
+ lnwire.GossipVersion1, 10, 0, 0, 0, node1, node2,
+ )
+ edge2, chanID2 := createEdge(
+ lnwire.GossipVersion1, 100, 0, 0, 0, node1, node2,
+ )
if err := graph.AddChannelEdge(ctx, edge1); err != nil {
t.Fatalf("unable to create channel edge: %v", err)
@@ -4723,7 +4731,9 @@ func TestBatchedAddChannelEdge(t *testing.T) {
// Create an edge which has its block height at 156.
height := uint32(156)
- edgeInfo, _ := createEdge(lnwire.GossipVersion1, height, 0, 0, 0, node1, node2)
+ edgeInfo, _ := createEdge(
+ lnwire.GossipVersion1, height, 0, 0, 0, node1, node2,
+ )
// Create an edge with block height 157. We give it
// maximum values for tx index and position, to make
diff --git a/graph/db/interfaces.go b/graph/db/interfaces.go
index b6a8c10..24cfae9 100644
--- a/graph/db/interfaces.go
+++ b/graph/db/interfaces.go
@@ -215,8 +215,9 @@ type Store interface { //nolint:interfacebloat
// 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.
- DeleteChannelEdges(strictZombiePruning, markZombie bool,
- chanIDs ...uint64) ([]*models.ChannelEdgeInfo, error)
+ DeleteChannelEdges(v lnwire.GossipVersion, strictZombiePruning,
+ markZombie bool, chanIDs ...uint64) (
+ []*models.ChannelEdgeInfo, error)
// AddEdgeProof sets the proof of an existing edge in the graph
// database.
diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go
index 0854a50..38b6c97 100644
--- a/graph/db/kv_store.go
+++ b/graph/db/kv_store.go
@@ -1889,8 +1889,13 @@ func (c *KVStore) PruneTip() (*chainhash.Hash, uint32, error) {
// 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 or not to mark the channel as a zombie.
-func (c *KVStore) DeleteChannelEdges(strictZombiePruning, markZombie bool,
- chanIDs ...uint64) ([]*models.ChannelEdgeInfo, error) {
+func (c *KVStore) DeleteChannelEdges(v lnwire.GossipVersion,
+ strictZombiePruning, markZombie bool, chanIDs ...uint64) (
+ []*models.ChannelEdgeInfo, error) {
+
+ if v != lnwire.GossipVersion1 {
+ return nil, ErrVersionNotSupportedForKVDB
+ }
// TODO(roasbeef): possibly delete from node bucket if node has no more
// channels
diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go
index cbb3d14..fe8aa62 100644
--- a/graph/db/sql_store.go
+++ b/graph/db/sql_store.go
@@ -1900,8 +1900,9 @@ func (s *SQLStore) NumZombies() (uint64, error) {
// denotes whether to mark the channel as a zombie.
//
// NOTE: part of the Store interface.
-func (s *SQLStore) DeleteChannelEdges(strictZombiePruning, markZombie bool,
- chanIDs ...uint64) ([]*models.ChannelEdgeInfo, error) {
+func (s *SQLStore) DeleteChannelEdges(v lnwire.GossipVersion,
+ strictZombiePruning, markZombie bool, chanIDs ...uint64) (
+ []*models.ChannelEdgeInfo, error) {
s.cacheMu.Lock()
defer s.cacheMu.Unlock()
@@ -1934,7 +1935,7 @@ func (s *SQLStore) DeleteChannelEdges(strictZombiePruning, markZombie bool,
}
err := s.forEachChanWithPoliciesInSCIDList(
- ctx, db, chanCallBack, chanIDs,
+ ctx, db, v, chanCallBack, chanIDs,
)
if err != nil {
return err
@@ -1962,7 +1963,7 @@ func (s *SQLStore) DeleteChannelEdges(strictZombiePruning, markZombie bool,
scid := byteOrder.Uint64(row.GraphChannel.Scid)
err := handleZombieMarking(
- ctx, db, row, edges[i],
+ ctx, db, v, row, edges[i],
strictZombiePruning, scid,
)
if err != nil {
@@ -2377,7 +2378,7 @@ func (s *SQLStore) FetchChanInfos(chanIDs []uint64) ([]ChannelEdge, error) {
}
err := s.forEachChanWithPoliciesInSCIDList(
- ctx, db, chanCallBack, chanIDs,
+ ctx, db, lnwire.GossipVersion1, chanCallBack, chanIDs,
)
if err != nil {
return err
@@ -2425,7 +2426,7 @@ func (s *SQLStore) FetchChanInfos(chanIDs []uint64) ([]ChannelEdge, error) {
// GetChannelsBySCIDWithPolicies query that allows us to iterate through
// channels in a paginated manner.
func (s *SQLStore) forEachChanWithPoliciesInSCIDList(ctx context.Context,
- db SQLQueries, cb func(ctx context.Context,
+ db SQLQueries, v lnwire.GossipVersion, cb func(ctx context.Context,
row sqlc.GetChannelsBySCIDWithPoliciesRow) error,
chanIDs []uint64) error {
@@ -2435,7 +2436,7 @@ func (s *SQLStore) forEachChanWithPoliciesInSCIDList(ctx context.Context,
return db.GetChannelsBySCIDWithPolicies(
ctx, sqlc.GetChannelsBySCIDWithPoliciesParams{
- Version: int16(lnwire.GossipVersion1),
+ Version: int16(v),
Scids: scids,
},
)
@@ -6027,12 +6028,19 @@ func batchBuildChannelInfo[T sqlc.ChannelAndNodeIDs](ctx context.Context,
// we are in strict zombie pruning mode, and adjusts the node public keys
// accordingly based on the last update timestamps of the channel policies.
func handleZombieMarking(ctx context.Context, db SQLQueries,
+ v lnwire.GossipVersion,
row sqlc.GetChannelsBySCIDWithPoliciesRow, info *models.ChannelEdgeInfo,
strictZombiePruning bool, scid uint64) error {
nodeKey1, nodeKey2 := info.NodeKey1Bytes, info.NodeKey2Bytes
if strictZombiePruning {
+ // TODO(elle): update for V2 last update times.
+ if v != lnwire.GossipVersion1 {
+ return fmt.Errorf("strict zombie pruning only "+
+ "supported for gossip v1, got %v", v)
+ }
+
var e1UpdateTime, e2UpdateTime *time.Time
if row.Policy1LastUpdate.Valid {
e1Time := time.Unix(row.Policy1LastUpdate.Int64, 0)
@@ -6051,7 +6059,7 @@ func handleZombieMarking(ctx context.Context, db SQLQueries,
return db.UpsertZombieChannel(
ctx, sqlc.UpsertZombieChannelParams{
- Version: int16(lnwire.GossipVersion1),
+ Version: int16(v),
Scid: channelIDToBytes(scid),
NodeKey1: nodeKey1[:],
NodeKey2: nodeKey2[:],
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.