What changed, and why it matters
This commit changes how LND's graph database filters channel ranges by adding a 'gossip version' parameter. It is a preparatory/infrastructure change: the older KV database now explicitly refuses version 2 requests, while the newer SQL database accepts them but still mostly uses the old version-1 query path (marked with a TODO for a future update). There is no obvious security vulnerability in the patch itself; it is more about making the code ready for future gossip protocol versions.
Treat as a normal feature/infrastructure commit. Review the follow-up TODO to replace GetPublicV1ChannelsBySCID with a version-aware SQL query, since leaving version filtering in application code could have performance or correctness implications. No immediate security patch is indicated by this commit alone.
Security signals we found
API signature change to enforce version-aware channel range filtering
KV store hard-rejects unsupported gossip versions with a sentinel error
SQL store filters by version in application code but still queries a v1-specific SQL helper (TODO noted)
Policy lookups in SQL store now use the requested gossip version instead of hard-coded GossipVersion1
New unit test covers version guard behavior for KV vs SQL backends
Evidence from the diff
FilterChannelRange is updated across the Store interface, ChannelGraph wrapper, KVStore, and SQLStore to accept a lnwire.GossipVersion. KVStore rejects anything other than GossipVersion1 with ErrVersionNotSupportedForKVDB. SQLStore accepts any known version, filters returned channels by version in Go code, and uses the requested version in policy lookups, though the underlying SQL query remains GetPublicV1ChannelsBySCID with a TODO to make it version-aware. A VersionedGraph wrapper shadows the method to keep the existing ChannelGraphTimeSeries interface unchanged. A new test verifies the KV guard and SQL graceful handling for v2.
Changed components
graph/db/Store interfacegraph/db/ChannelGraph.FilterChannelRangegraph/db/VersionedGraph.FilterChannelRangegraph/db/KVStore.FilterChannelRangegraph/db/SQLStore.FilterChannelRangegraph/db/graph_test.goInspect captured patch +72 / −22
diff --git a/graph/db/graph.go b/graph/db/graph.go
index f564d88..be0ac82 100644
--- a/graph/db/graph.go
+++ b/graph/db/graph.go
@@ -717,13 +717,25 @@ func (c *ChannelGraph) ChanUpdatesInHorizon(ctx context.Context,
return c.db.ChanUpdatesInHorizon(ctx, startTime, endTime, opts...)
}
-// FilterChannelRange returns channel IDs within the passed block height range.
+// FilterChannelRange returns channel IDs within the passed block height range
+// for the given gossip version.
func (c *ChannelGraph) FilterChannelRange(ctx context.Context,
- startHeight, endHeight uint32, withTimestamps bool) (
- []BlockChannelRange, error) {
+ v lnwire.GossipVersion, startHeight, endHeight uint32,
+ withTimestamps bool) ([]BlockChannelRange, error) {
return c.db.FilterChannelRange(
- ctx, startHeight, endHeight, withTimestamps,
+ ctx, v, startHeight, endHeight, withTimestamps,
+ )
+}
+
+// FilterChannelRange returns channel IDs within the passed block height range
+// for this graph's gossip version.
+func (c *VersionedGraph) FilterChannelRange(ctx context.Context,
+ startHeight, endHeight uint32,
+ withTimestamps bool) ([]BlockChannelRange, error) {
+
+ return c.db.FilterChannelRange(
+ ctx, c.v, startHeight, endHeight, withTimestamps,
)
}
diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go
index 90a2fc5..a7bc4d3 100644
--- a/graph/db/graph_test.go
+++ b/graph/db/graph_test.go
@@ -3505,7 +3505,9 @@ func TestFilterChannelRange(t *testing.T) {
// If we try to filter a channel range before we have any channels
// inserted, we should get an empty slice of results.
- resp, err := graph.FilterChannelRange(ctx, 10, 100, false)
+ resp, err := graph.FilterChannelRange(
+ ctx, lnwire.GossipVersion1, 10, 100, false,
+ )
require.NoError(t, err)
require.Empty(t, resp)
@@ -3676,7 +3678,8 @@ func TestFilterChannelRange(t *testing.T) {
// First, do the query without requesting timestamps.
resp, err := graph.FilterChannelRange(
- ctx, test.startHeight, test.endHeight, false,
+ ctx, lnwire.GossipVersion1, test.startHeight,
+ test.endHeight, false,
)
require.NoError(t, err)
@@ -3690,7 +3693,8 @@ func TestFilterChannelRange(t *testing.T) {
// Now, query the timestamps as well.
resp, err = graph.FilterChannelRange(
- ctx, test.startHeight, test.endHeight, true,
+ ctx, lnwire.GossipVersion1, test.startHeight,
+ test.endHeight, true,
)
require.NoError(t, err)
@@ -3705,6 +3709,29 @@ func TestFilterChannelRange(t *testing.T) {
}
}
+// TestFilterChannelRangeVersionGuard checks that FilterChannelRange correctly
+// handles version-specific requests. For gossip v1, the KV store returns
+// results as normal; for v2, the KV store returns
+// ErrVersionNotSupportedForKVDB while the SQL store returns empty results
+// (a v2-aware query is a follow-up).
+func TestFilterChannelRangeVersionGuard(t *testing.T) {
+ t.Parallel()
+ ctx := t.Context()
+
+ store := NewTestDB(t)
+
+ _, err := store.FilterChannelRange(
+ ctx, lnwire.GossipVersion2, 0, 1000, false,
+ )
+
+ // The KV store does not support v2 and must return the sentinel error.
+ // The SQL store accepts any known version (returning empty results
+ // since no v2 channels have been added).
+ if err != nil {
+ require.ErrorIs(t, err, ErrVersionNotSupportedForKVDB)
+ }
+}
+
// TestFetchChanInfos tests that we're able to properly retrieve the full set
// of ChannelEdge structs for a given set of short channel ID's.
func testFetchChanInfos(t *testing.T, v lnwire.GossipVersion) {
diff --git a/graph/db/interfaces.go b/graph/db/interfaces.go
index e701e61..00ced9c 100644
--- a/graph/db/interfaces.go
+++ b/graph/db/interfaces.go
@@ -273,15 +273,15 @@ type Store interface { //nolint:interfacebloat
error)
// FilterChannelRange returns the channel ID's of all known channels
- // which were mined in a block height within the passed range. The
- // channel IDs are grouped by their common block height. This method can
- // be used to quickly share with a peer the set of channels we know of
- // within a particular range to catch them up after a period of time
- // offline. If withTimestamps is true then the timestamp info of the
- // latest received channel update messages of the channel will be
- // included in the response.
- FilterChannelRange(ctx context.Context, startHeight,
- endHeight uint32,
+ // which were mined in a block height within the passed range for the
+ // given gossip version. The channel IDs are grouped by their common
+ // block height. This method can be used to quickly share with a peer
+ // the set of channels we know of within a particular range to catch
+ // them up after a period of time offline. If withTimestamps is true
+ // then the timestamp info of the latest received channel update
+ // messages of the channel will be included in the response.
+ FilterChannelRange(ctx context.Context, v lnwire.GossipVersion,
+ startHeight, endHeight uint32,
withTimestamps bool) ([]BlockChannelRange, error)
// FetchChanInfos returns the set of channel edges that correspond to
diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go
index cd8a928..8e6ba3b 100644
--- a/graph/db/kv_store.go
+++ b/graph/db/kv_store.go
@@ -2859,8 +2859,13 @@ type BlockChannelRange struct {
// up after a period of time offline. If withTimestamps is true then the
// timestamp info of the latest received channel update messages of the channel
// will be included in the response.
-func (c *KVStore) FilterChannelRange(_ context.Context, startHeight,
- endHeight uint32, withTimestamps bool) ([]BlockChannelRange, error) {
+func (c *KVStore) FilterChannelRange(_ context.Context,
+ v lnwire.GossipVersion, startHeight, endHeight uint32,
+ withTimestamps bool) ([]BlockChannelRange, error) {
+
+ if v != lnwire.GossipVersion1 {
+ return nil, ErrVersionNotSupportedForKVDB
+ }
startChanID := &lnwire.ShortChannelID{
BlockHeight: startHeight,
diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go
index c9e7384..c2d8a70 100644
--- a/graph/db/sql_store.go
+++ b/graph/db/sql_store.go
@@ -1664,8 +1664,9 @@ func (s *SQLStore) ForEachChannel(ctx context.Context,
// will be included in the response.
//
// NOTE: This is part of the Store interface.
-func (s *SQLStore) FilterChannelRange(ctx context.Context, startHeight,
- endHeight uint32, withTimestamps bool) ([]BlockChannelRange, error) {
+func (s *SQLStore) FilterChannelRange(ctx context.Context,
+ v lnwire.GossipVersion, startHeight, endHeight uint32,
+ withTimestamps bool) ([]BlockChannelRange, error) {
var (
startSCID = &lnwire.ShortChannelID{
@@ -1687,6 +1688,7 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context, startHeight,
// and add those timestamps to the collected channel.
channelsPerBlock := make(map[uint32][]ChannelUpdateInfo)
err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error {
+ // TODO(elle): replace with a version-aware query.
dbChans, err := db.GetPublicV1ChannelsBySCID(
ctx, sqlc.GetPublicV1ChannelsBySCIDParams{
StartScid: chanIDStart,
@@ -1699,6 +1701,10 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context, startHeight,
}
for _, dbChan := range dbChans {
+ if v != lnwire.GossipVersion(dbChan.Version) {
+ continue
+ }
+
cid := lnwire.NewShortChanIDFromInt(
byteOrder.Uint64(dbChan.Scid),
)
@@ -1718,7 +1724,7 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context, startHeight,
//nolint:ll
node1Policy, err := db.GetChannelPolicyByChannelAndNode(
ctx, sqlc.GetChannelPolicyByChannelAndNodeParams{
- Version: int16(lnwire.GossipVersion1),
+ Version: int16(v),
ChannelID: dbChan.ID,
NodeID: dbChan.NodeID1,
},
@@ -1735,7 +1741,7 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context, startHeight,
//nolint:ll
node2Policy, err := db.GetChannelPolicyByChannelAndNode(
ctx, sqlc.GetChannelPolicyByChannelAndNodeParams{
- Version: int16(lnwire.GossipVersion1),
+ Version: int16(v),
ChannelID: dbChan.ID,
NodeID: dbChan.NodeID2,
},
Why this scored 28/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.