graph/db: version cacheable iteration methods

Add gossip version parameters to ForEachNodeCacheable and
ForEachChannelCacheable in the Store interface and both implementations.

Thread the version through SQL helpers and update call sites/tests
accordingly.
This commit is contained in:
Elle Mouton committed 2026-02-20 08:50:43 +02:00
1 parent d48a16036d
commit f5c91691bc
7 files changed
+83 -54

No files matched your search

+2 -1
View File
@@ -669,7 +669,8 @@ func BenchmarkGraphReadMethods(b *testing.B) {
name: "ForEachNodeCacheable", name: "ForEachNodeCacheable",
fn: func(b testing.TB, store Store) { fn: func(b testing.TB, store Store) {
err := store.ForEachNodeCacheable( err := store.ForEachNodeCacheable(
ctx, func(_ route.Vertex, ctx, lnwire.GossipVersion1,
func(_ route.Vertex,
_ *lnwire.FeatureVector) error { _ *lnwire.FeatureVector) error {
// Increment the counter to // Increment the counter to
+29 -17
View File
@@ -163,19 +163,23 @@ func (c *ChannelGraph) populateCache(ctx context.Context) error {
log.Info("Populating in-memory channel graph, this might take a " + log.Info("Populating in-memory channel graph, this might take a " +
"while...") "while...")
err := c.db.ForEachNodeCacheable(ctx, func(node route.Vertex, // TODO(elle): the cache should be populated with data from across
features *lnwire.FeatureVector) error { // protocol versions.
err := c.db.ForEachNodeCacheable(
ctx, lnwire.GossipVersion1, func(node route.Vertex,
features *lnwire.FeatureVector) error {
c.graphCache.AddNodeFeatures(node, features) c.graphCache.AddNodeFeatures(node, features)
return nil return nil
}, func() {}) }, func() {},
)
if err != nil { if err != nil {
return err return err
} }
err = c.db.ForEachChannelCacheable( err = c.db.ForEachChannelCacheable(
func(info *models.CachedEdgeInfo, lnwire.GossipVersion1, func(info *models.CachedEdgeInfo,
policy1, policy2 *models.CachedEdgePolicy) error { policy1, policy2 *models.CachedEdgePolicy) error {
c.graphCache.AddChannel(info, policy1, policy2) c.graphCache.AddChannel(info, policy1, policy2)
@@ -606,10 +610,10 @@ func (c *ChannelGraph) ForEachNode(ctx context.Context,
// ForEachNodeCacheable iterates through all stored vertices/nodes in the graph. // ForEachNodeCacheable iterates through all stored vertices/nodes in the graph.
func (c *ChannelGraph) ForEachNodeCacheable(ctx context.Context, func (c *ChannelGraph) ForEachNodeCacheable(ctx context.Context,
cb func(route.Vertex, *lnwire.FeatureVector) error, v lnwire.GossipVersion, cb func(route.Vertex,
reset func()) error { *lnwire.FeatureVector) error, reset func()) error {
return c.db.ForEachNodeCacheable(ctx, cb, reset) return c.db.ForEachNodeCacheable(ctx, v, cb, reset)
} }
// NodeUpdatesInHorizon returns all known lightning nodes with updates in the // NodeUpdatesInHorizon returns all known lightning nodes with updates in the
@@ -642,14 +646,6 @@ func (c *ChannelGraph) ForEachChannel(ctx context.Context,
return c.db.ForEachChannel(ctx, v, cb, reset) return c.db.ForEachChannel(ctx, v, cb, reset)
} }
// ForEachChannelCacheable iterates through all channel edges for the cache.
func (c *ChannelGraph) ForEachChannelCacheable(cb func(*models.CachedEdgeInfo,
*models.CachedEdgePolicy, *models.CachedEdgePolicy) error,
reset func()) error {
return c.db.ForEachChannelCacheable(cb, reset)
}
// DisabledChannelIDs returns the channel ids of disabled channels. // DisabledChannelIDs returns the channel ids of disabled channels.
func (c *ChannelGraph) DisabledChannelIDs() ([]uint64, error) { func (c *ChannelGraph) DisabledChannelIDs() ([]uint64, error) {
return c.db.DisabledChannelIDs() return c.db.DisabledChannelIDs()
@@ -923,6 +919,22 @@ func (c *VersionedGraph) ForEachChannel(ctx context.Context,
return c.db.ForEachChannel(ctx, c.v, cb, reset) return c.db.ForEachChannel(ctx, c.v, cb, reset)
} }
// ForEachNodeCacheable iterates through all stored vertices/nodes in the graph.
func (c *VersionedGraph) ForEachNodeCacheable(ctx context.Context,
cb func(route.Vertex, *lnwire.FeatureVector) error,
reset func()) error {
return c.db.ForEachNodeCacheable(ctx, c.v, cb, reset)
}
// ForEachChannelCacheable iterates through all channel edges for the cache.
func (c *VersionedGraph) ForEachChannelCacheable(
cb func(*models.CachedEdgeInfo, *models.CachedEdgePolicy,
*models.CachedEdgePolicy) error, reset func()) error {
return c.db.ForEachChannelCacheable(c.v, cb, reset)
}
// IsPublicNode determines whether the node is seen as public in the graph. // IsPublicNode determines whether the node is seen as public in the graph.
func (c *VersionedGraph) IsPublicNode(pubKey [33]byte) (bool, error) { func (c *VersionedGraph) IsPublicNode(pubKey [33]byte) (bool, error) {
return c.db.IsPublicNode(c.v, pubKey) return c.db.IsPublicNode(c.v, pubKey)
+17 -15
View File
@@ -1847,16 +1847,17 @@ func TestGraphTraversalCacheable(t *testing.T) {
// iterating over each node, once again if the map is empty that // iterating over each node, once again if the map is empty that
// indicates that all edges have properly been reached. // indicates that all edges have properly been reached.
var nodes []route.Vertex var nodes []route.Vertex
err = graph.ForEachNodeCacheable(ctx, func(node route.Vertex, err = graph.ForEachNodeCacheable(
features *lnwire.FeatureVector) error { ctx, lnwire.GossipVersion1, func(node route.Vertex,
features *lnwire.FeatureVector) error {
delete(nodeMap, node) delete(nodeMap, node)
nodes = append(nodes, node) nodes = append(nodes, node)
return nil return nil
}, func() { }, func() {
nodes = nil nodes = nil
}) })
require.NoError(t, err) require.NoError(t, err)
require.Len(t, nodeMap, 0) require.Len(t, nodeMap, 0)
@@ -5062,15 +5063,16 @@ func BenchmarkForEachChannel(b *testing.B) {
) )
var nodes []route.Vertex var nodes []route.Vertex
err := graph.ForEachNodeCacheable(ctx, func(node route.Vertex, err := graph.ForEachNodeCacheable(
vector *lnwire.FeatureVector) error { ctx, lnwire.GossipVersion1, func(node route.Vertex,
vector *lnwire.FeatureVector) error {
nodes = append(nodes, node) nodes = append(nodes, node)
return nil return nil
}, func() { }, func() {
nodes = nil nodes = nil
}) })
require.NoError(b, err) require.NoError(b, err)
for _, n := range nodes { for _, n := range nodes {
+6 -5
View File
@@ -103,8 +103,9 @@ type Store interface { //nolint:interfacebloat
// in the graph, executing the passed callback with each node // in the graph, executing the passed callback with each node
// encountered. If the callback returns an error, then the transaction // encountered. If the callback returns an error, then the transaction
// is aborted and the iteration stops early. // is aborted and the iteration stops early.
ForEachNodeCacheable(ctx context.Context, cb func(route.Vertex, ForEachNodeCacheable(ctx context.Context, v lnwire.GossipVersion,
*lnwire.FeatureVector) error, reset func()) error cb func(route.Vertex, *lnwire.FeatureVector) error,
reset func()) error
// LookupAlias attempts to return the alias as advertised by the target // LookupAlias attempts to return the alias as advertised by the target
// node. // node.
@@ -182,9 +183,9 @@ type Store interface { //nolint:interfacebloat
// //
// NOTE: this method is like ForEachChannel but fetches only the data // NOTE: this method is like ForEachChannel but fetches only the data
// required for the graph cache. // required for the graph cache.
ForEachChannelCacheable(cb func(*models.CachedEdgeInfo, ForEachChannelCacheable(v lnwire.GossipVersion,
*models.CachedEdgePolicy, *models.CachedEdgePolicy) error, cb func(*models.CachedEdgeInfo, *models.CachedEdgePolicy,
reset func()) error *models.CachedEdgePolicy) error, reset func()) error
// DisabledChannelIDs returns the channel ids of disabled channels. // DisabledChannelIDs returns the channel ids of disabled channels.
// A channel is disabled when two of the associated ChanelEdgePolicies // A channel is disabled when two of the associated ChanelEdgePolicies
+13 -5
View File
@@ -496,9 +496,13 @@ func forEachChannel(db kvdb.Backend, cb func(*models.ChannelEdgeInfo,
// //
// NOTE: this method is like ForEachChannel but fetches only the data required // NOTE: this method is like ForEachChannel but fetches only the data required
// for the graph cache. // for the graph cache.
func (c *KVStore) ForEachChannelCacheable(cb func(*models.CachedEdgeInfo, func (c *KVStore) ForEachChannelCacheable(v lnwire.GossipVersion,
*models.CachedEdgePolicy, *models.CachedEdgePolicy) error, cb func(*models.CachedEdgeInfo, *models.CachedEdgePolicy,
reset func()) error { *models.CachedEdgePolicy) error, reset func()) error {
if v != lnwire.GossipVersion1 {
return ErrVersionNotSupportedForKVDB
}
return c.db.View(func(tx kvdb.RTx) error { return c.db.View(func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket) edges := tx.ReadBucket(edgeBucket)
@@ -874,8 +878,12 @@ func forEachNode(db kvdb.Backend,
// callback returns an error, then the transaction is aborted and the iteration // callback returns an error, then the transaction is aborted and the iteration
// stops early. // stops early.
func (c *KVStore) ForEachNodeCacheable(_ context.Context, func (c *KVStore) ForEachNodeCacheable(_ context.Context,
cb func(route.Vertex, *lnwire.FeatureVector) error, v lnwire.GossipVersion, cb func(route.Vertex,
reset func()) error { *lnwire.FeatureVector) error, reset func()) error {
if v != lnwire.GossipVersion1 {
return ErrVersionNotSupportedForKVDB
}
traversal := func(tx kvdb.RTx) error { traversal := func(tx kvdb.RTx) error {
// First grab the nodes bucket which stores the mapping from // First grab the nodes bucket which stores the mapping from
+15 -10
View File
@@ -1003,12 +1003,12 @@ func (s *SQLStore) ForEachNodeDirectedChannel(nodePub route.Vertex,
// callback returns an error, then the transaction is aborted and the iteration // callback returns an error, then the transaction is aborted and the iteration
// stops early. // stops early.
func (s *SQLStore) ForEachNodeCacheable(ctx context.Context, func (s *SQLStore) ForEachNodeCacheable(ctx context.Context,
cb func(route.Vertex, *lnwire.FeatureVector) error, v lnwire.GossipVersion, cb func(route.Vertex,
reset func()) error { *lnwire.FeatureVector) error, reset func()) error {
err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error {
return forEachNodeCacheable( return forEachNodeCacheable(
ctx, s.cfg.QueryCfg, db, ctx, s.cfg.QueryCfg, db, v,
func(_ int64, nodePub route.Vertex, func(_ int64, nodePub route.Vertex,
features *lnwire.FeatureVector) error { features *lnwire.FeatureVector) error {
@@ -1547,12 +1547,16 @@ func (s *SQLStore) ForEachNodeCached(ctx context.Context, withAddrs bool,
// //
// NOTE: this method is like ForEachChannel but fetches only the data // NOTE: this method is like ForEachChannel but fetches only the data
// required for the graph cache. // required for the graph cache.
func (s *SQLStore) ForEachChannelCacheable(cb func(*models.CachedEdgeInfo, func (s *SQLStore) ForEachChannelCacheable(v lnwire.GossipVersion,
*models.CachedEdgePolicy, *models.CachedEdgePolicy) error, cb func(*models.CachedEdgeInfo, *models.CachedEdgePolicy,
reset func()) error { *models.CachedEdgePolicy) error, reset func()) error {
ctx := context.TODO() ctx := context.TODO()
if !isKnownGossipVersion(v) {
return fmt.Errorf("unsupported gossip version: %d", v)
}
handleChannel := func(_ context.Context, handleChannel := func(_ context.Context,
row sqlc.ListChannelsWithPoliciesForCachePaginatedRow) error { row sqlc.ListChannelsWithPoliciesForCachePaginatedRow) error {
@@ -1596,7 +1600,7 @@ func (s *SQLStore) ForEachChannelCacheable(cb func(*models.CachedEdgeInfo,
return db.ListChannelsWithPoliciesForCachePaginated( return db.ListChannelsWithPoliciesForCachePaginated(
ctx, sqlc.ListChannelsWithPoliciesForCachePaginatedParams{ ctx, sqlc.ListChannelsWithPoliciesForCachePaginatedParams{
Version: int16(lnwire.GossipVersion1), Version: int16(v),
ID: lastID, ID: lastID,
Limit: limit, Limit: limit,
}, },
@@ -3475,11 +3479,12 @@ func forEachNodeDirectedChannel(ctx context.Context, db SQLQueries,
return nil return nil
} }
// forEachNodeCacheable fetches all V1 node IDs and pub keys from the database, // forEachNodeCacheable fetches all node IDs and pub keys from the database,
// and executes the provided callback for each node. It does so via pagination // and executes the provided callback for each node. It does so via pagination
// along with batch loading of the node feature bits. // along with batch loading of the node feature bits.
func forEachNodeCacheable(ctx context.Context, cfg *sqldb.QueryConfig, func forEachNodeCacheable(ctx context.Context, cfg *sqldb.QueryConfig,
db SQLQueries, processNode func(nodeID int64, nodePub route.Vertex, db SQLQueries, v lnwire.GossipVersion,
processNode func(nodeID int64, nodePub route.Vertex,
features *lnwire.FeatureVector) error) error { features *lnwire.FeatureVector) error) error {
handleNode := func(_ context.Context, handleNode := func(_ context.Context,
@@ -3504,7 +3509,7 @@ func forEachNodeCacheable(ctx context.Context, cfg *sqldb.QueryConfig,
return db.ListNodeIDsAndPubKeys( return db.ListNodeIDsAndPubKeys(
ctx, sqlc.ListNodeIDsAndPubKeysParams{ ctx, sqlc.ListNodeIDsAndPubKeysParams{
Version: int16(lnwire.GossipVersion1), Version: int16(v),
ID: lastID, ID: lastID,
Limit: limit, Limit: limit,
}, },
+1 -1
View File
@@ -1128,7 +1128,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
DefaultRoutingPolicy: cc.RoutingPolicy, DefaultRoutingPolicy: cc.RoutingPolicy,
ForAllOutgoingChannels: func(ctx context.Context, ForAllOutgoingChannels: func(ctx context.Context,
cb func(*models.ChannelEdgeInfo, cb func(*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy) error, *models.ChannelEdgePolicy) error,
reset func()) error { reset func()) error {
return s.v1Graph.ForEachNodeChannel( return s.v1Graph.ForEachNodeChannel(