From 3c06daed8bb2f4e3f11658ec6cc6664f75b7d52c Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 3 Mar 2026 14:12:53 +0200 Subject: [PATCH] graph/db: version FilterChannelRange Add a gossip version parameter to FilterChannelRange in the Store interface, both KV and SQL implementations, and the ChannelGraph wrapper. KVStore guards against non-v1 versions with ErrVersionNotSupportedForKVDB. SQLStore accepts any known gossip version, filtering the channel results by version and using it in policy lookups. The SQL query still uses GetPublicV1ChannelsBySCID for now (a TODO marks where a version-aware query will be substituted in a follow-up). VersionedGraph.FilterChannelRange shadows the ChannelGraph method with a version-free signature, passing its baked-in version to the store. This keeps the ChannelGraphTimeSeries interface and ChanSeries implementation unchanged. Add TestFilterChannelRangeVersionGuard to verify that the KV store returns ErrVersionNotSupportedForKVDB for v2 requests while the SQL store handles them gracefully. --- graph/db/graph.go | 20 ++++++++++++++++---- graph/db/graph_test.go | 33 ++++++++++++++++++++++++++++++--- graph/db/interfaces.go | 18 +++++++++--------- graph/db/kv_store.go | 9 +++++++-- graph/db/sql_store.go | 14 ++++++++++---- 5 files changed, 72 insertions(+), 22 deletions(-) diff --git a/graph/db/graph.go b/graph/db/graph.go index f564d8884..be0ac82d0 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 90a2fc593..a7bc4d3ad 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 e701e61c8..00ced9c8f 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 cd8a9289d..8e6ba3b89 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 c9e738436..c2d8a7076 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, },