graph/db: version ForEachSourceNodeChannel

Add a gossip version parameter to ForEachSourceNodeChannel in the Store
interface and both KV/SQL implementations. The VersionedGraph wrapper
delegates with its baked-in version. Convert the
testAddChannelEdgeShellNodes and testForEachSourceNodeChannel tests to
run against both v1 and v2 gossip versions.
This commit is contained in:
Elle Mouton committed 2026-02-20 08:46:58 +02:00
1 parent 2aaa52adf9
commit 3ea2423f25
6 files changed
+85 -59

No files matched your search

+8 -8
View File
@@ -587,14 +587,6 @@ func (c *ChannelGraph) UpdateEdgePolicy(ctx context.Context,
return nil return nil
} }
// ForEachSourceNodeChannel iterates through all channels of the source node.
func (c *ChannelGraph) ForEachSourceNodeChannel(ctx context.Context,
cb func(chanPoint wire.OutPoint, havePolicy bool,
otherNode *models.Node) error, reset func()) error {
return c.db.ForEachSourceNodeChannel(ctx, cb, reset)
}
// ForEachNodeChannel iterates through all channels of the given node. // ForEachNodeChannel iterates through all channels of the given node.
func (c *ChannelGraph) ForEachNodeChannel(ctx context.Context, func (c *ChannelGraph) ForEachNodeChannel(ctx context.Context,
v lnwire.GossipVersion, nodePub route.Vertex, v lnwire.GossipVersion, nodePub route.Vertex,
@@ -906,6 +898,14 @@ func (c *VersionedGraph) HasChannelEdge(chanID uint64) (bool, bool, error) {
return c.db.HasChannelEdge(c.v, chanID) return c.db.HasChannelEdge(c.v, chanID)
} }
// ForEachSourceNodeChannel iterates through all channels of the source node.
func (c *VersionedGraph) ForEachSourceNodeChannel(ctx context.Context,
cb func(chanPoint wire.OutPoint, havePolicy bool,
otherNode *models.Node) error, reset func()) error {
return c.db.ForEachSourceNodeChannel(ctx, c.v, cb, reset)
}
// ForEachNodeChannel iterates through all channels of the given node. // ForEachNodeChannel iterates through all channels of the given node.
func (c *VersionedGraph) ForEachNodeChannel(ctx context.Context, func (c *VersionedGraph) ForEachNodeChannel(ctx context.Context,
nodePub route.Vertex, cb func(*models.ChannelEdgeInfo, nodePub route.Vertex, cb func(*models.ChannelEdgeInfo,
+57 -38
View File
@@ -154,6 +154,14 @@ var versionedTests = []versionedTest{
name: "incomplete channel policies", name: "incomplete channel policies",
test: testIncompleteChannelPolicies, test: testIncompleteChannelPolicies,
}, },
{
name: "add channel edge shell nodes",
test: testAddChannelEdgeShellNodes,
},
{
name: "for each source node channel",
test: testForEachSourceNodeChannel,
},
{ {
name: "partial node", name: "partial node",
test: testPartialNode, test: testPartialNode,
@@ -1605,16 +1613,16 @@ func testAddEdgeProof(t *testing.T, v lnwire.GossipVersion) {
require.NotNil(t, dbEdge2.AuthProof) require.NotNil(t, dbEdge2.AuthProof)
} }
// TestForEachSourceNodeChannel tests that the ForEachSourceNodeChannel // testForEachSourceNodeChannel tests that the ForEachSourceNodeChannel
// correctly iterates through the channels of the set source node. // correctly iterates through the channels of the set source node.
func TestForEachSourceNodeChannel(t *testing.T) { func testForEachSourceNodeChannel(t *testing.T, v lnwire.GossipVersion) {
t.Parallel() t.Parallel()
ctx := t.Context() ctx := t.Context()
graph := MakeTestGraph(t) graph := NewVersionedGraph(MakeTestGraph(t), v)
// Create a source node (A) and set it as such in the DB. // Create a source node (A) and set it as such in the DB.
nodeA := createTestVertex(t, lnwire.GossipVersion1) nodeA := createTestVertex(t, v)
require.NoError(t, graph.SetSourceNode(ctx, nodeA)) require.NoError(t, graph.SetSourceNode(ctx, nodeA))
// Now, create a few more nodes (B, C, D) along with some channels // Now, create a few more nodes (B, C, D) along with some channels
@@ -1630,32 +1638,40 @@ func TestForEachSourceNodeChannel(t *testing.T) {
// outgoing policy but for the A-C channel, we will set only an incoming // outgoing policy but for the A-C channel, we will set only an incoming
// policy. // policy.
nodeB := createTestVertex(t, lnwire.GossipVersion1) nodeB := createTestVertex(t, v)
nodeC := createTestVertex(t, lnwire.GossipVersion1) nodeC := createTestVertex(t, v)
nodeD := createTestVertex(t, lnwire.GossipVersion1) nodeD := createTestVertex(t, v)
abEdge, abPolicy1, abPolicy2 := createChannelEdge(nodeA, nodeB) abEdge, _ := createEdge(v, 100, 0, 0, 0, nodeA, nodeB)
require.NoError(t, graph.AddChannelEdge(ctx, abEdge)) require.NoError(t, graph.AddChannelEdge(ctx, abEdge))
acEdge, acPolicy1, acPolicy2 := createChannelEdge(nodeA, nodeC) acEdge, _ := createEdge(v, 200, 0, 0, 1, nodeA, nodeC)
require.NoError(t, graph.AddChannelEdge(ctx, acEdge)) require.NoError(t, graph.AddChannelEdge(ctx, acEdge))
bdEdge, _, _ := createChannelEdge(nodeB, nodeD) bdEdge, _ := createEdge(v, 300, 0, 0, 2, nodeB, nodeD)
require.NoError(t, graph.AddChannelEdge(ctx, bdEdge)) require.NoError(t, graph.AddChannelEdge(ctx, bdEdge))
// Figure out which of the policies returned above are node A's so that newPolicy := func(edge *models.ChannelEdgeInfo, fromNode,
// we know which to persist. toNode route.Vertex) *models.ChannelEdgePolicy {
//
// First, set the outgoing policy for the A-B channel. isNode1 := bytes.Equal(fromNode[:], edge.NodeKey1Bytes[:])
abPolicyAOutgoing := abPolicy1 policy := newEdgePolicy(
if !bytes.Equal(abPolicy1.ToNode[:], nodeB.PubKeyBytes[:]) { v, edge.ChannelID, nextUpdateTime().Unix(), isNode1,
abPolicyAOutgoing = abPolicy2 )
policy.ToNode = toNode
policy.SigBytes = testSig.Serialize()
return policy
} }
// First, set the outgoing policy for the A-B channel.
abPolicyAOutgoing := newPolicy(
abEdge, nodeA.PubKeyBytes, nodeB.PubKeyBytes,
)
require.NoError(t, graph.UpdateEdgePolicy(ctx, abPolicyAOutgoing)) require.NoError(t, graph.UpdateEdgePolicy(ctx, abPolicyAOutgoing))
// Now, set the incoming policy for the A-C channel. // Now, set the incoming policy for the A-C channel.
acPolicyAIncoming := acPolicy1 acPolicyAIncoming := newPolicy(
if !bytes.Equal(acPolicy1.ToNode[:], nodeA.PubKeyBytes[:]) { acEdge, nodeC.PubKeyBytes, nodeA.PubKeyBytes,
acPolicyAIncoming = acPolicy2 )
}
require.NoError(t, graph.UpdateEdgePolicy(ctx, acPolicyAIncoming)) require.NoError(t, graph.UpdateEdgePolicy(ctx, acPolicyAIncoming))
type sourceNodeChan struct { type sourceNodeChan struct {
@@ -1677,21 +1693,24 @@ func TestForEachSourceNodeChannel(t *testing.T) {
// Now, we'll use the ForEachSourceNodeChannel and assert that it // Now, we'll use the ForEachSourceNodeChannel and assert that it
// returns the expected data in the call-back. // returns the expected data in the call-back.
err := graph.ForEachSourceNodeChannel(ctx, func(chanPoint wire.OutPoint, err := graph.ForEachSourceNodeChannel(
havePolicy bool, otherNode *models.Node) error { ctx, func(chanPoint wire.OutPoint, havePolicy bool,
otherNode *models.Node) error {
require.Contains(t, expectedSrcChans, chanPoint) require.Contains(t, expectedSrcChans, chanPoint)
expected := expectedSrcChans[chanPoint] expected := expectedSrcChans[chanPoint]
require.Equal( require.Equal(
t, expected.otherNode[:], otherNode.PubKeyBytes[:], t, expected.otherNode[:],
) otherNode.PubKeyBytes[:],
require.Equal(t, expected.havePolicy, havePolicy) )
require.Equal(t, expected.havePolicy, havePolicy)
delete(expectedSrcChans, chanPoint) delete(expectedSrcChans, chanPoint)
return nil return nil
}, func() {}) }, func() {},
)
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, expectedSrcChans) require.Empty(t, expectedSrcChans)
} }
@@ -4106,25 +4125,25 @@ func TestPruneGraphNodes(t *testing.T) {
require.NotNil(t, err) require.NotNil(t, err)
} }
// TestAddChannelEdgeShellNodes tests that when we attempt to add a ChannelEdge // testAddChannelEdgeShellNodes tests that when we attempt to add a ChannelEdge
// to the graph, one or both of the nodes the edge involves aren't found in the // to the graph, one or both of the nodes the edge involves aren't found in the
// database, then shell edges are created for each node if needed. // database, then shell edges are created for each node if needed.
func TestAddChannelEdgeShellNodes(t *testing.T) { func testAddChannelEdgeShellNodes(t *testing.T, v lnwire.GossipVersion) {
t.Parallel() t.Parallel()
ctx := t.Context() ctx := t.Context()
graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) graph := NewVersionedGraph(MakeTestGraph(t), v)
// To start, we'll create two nodes, and only add one of them to the // To start, we'll create two nodes, and only add one of them to the
// channel graph. // channel graph.
node1 := createTestVertex(t, lnwire.GossipVersion1) node1 := createTestVertex(t, v)
require.NoError(t, graph.SetSourceNode(ctx, node1)) require.NoError(t, graph.SetSourceNode(ctx, node1))
node2 := createTestVertex(t, lnwire.GossipVersion1) node2 := createTestVertex(t, v)
// We'll now create an edge between the two nodes, as a result, node2 // We'll now create an edge between the two nodes, as a result, node2
// should be inserted into the database as a shell node. // should be inserted into the database as a shell node.
edgeInfo, _ := createEdge( edgeInfo, _ := createEdge(
lnwire.GossipVersion1, 100, 0, 0, 0, node1, node2, v, 100, 0, 0, 0, node1, node2,
) )
require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo))
+1 -1
View File
@@ -59,7 +59,7 @@ type Store interface { //nolint:interfacebloat
// node, executing the passed callback on each. The call-back is // node, executing the passed callback on each. The call-back is
// provided with the channel's outpoint, whether we have a policy for // provided with the channel's outpoint, whether we have a policy for
// the channel and the channel peer's node information. // the channel and the channel peer's node information.
ForEachSourceNodeChannel(ctx context.Context, ForEachSourceNodeChannel(ctx context.Context, v lnwire.GossipVersion,
cb func(chanPoint wire.OutPoint, havePolicy bool, cb func(chanPoint wire.OutPoint, havePolicy bool,
otherNode *models.Node) error, otherNode *models.Node) error,
reset func()) error reset func()) error
+7 -2
View File
@@ -3722,8 +3722,13 @@ func (c *KVStore) ForEachNodeChannel(_ context.Context,
// channel's outpoint, whether we have a policy for the channel and the channel // channel's outpoint, whether we have a policy for the channel and the channel
// peer's node information. // peer's node information.
func (c *KVStore) ForEachSourceNodeChannel(_ context.Context, func (c *KVStore) ForEachSourceNodeChannel(_ context.Context,
cb func(chanPoint wire.OutPoint, havePolicy bool, v lnwire.GossipVersion, cb func(chanPoint wire.OutPoint,
otherNode *models.Node) error, reset func()) error { havePolicy bool, otherNode *models.Node) error,
reset func()) error {
if v != lnwire.GossipVersion1 {
return ErrVersionNotSupportedForKVDB
}
return kvdb.View(c.db, func(tx kvdb.RTx) error { return kvdb.View(c.db, func(tx kvdb.RTx) error {
nodes := tx.ReadBucket(nodeBucket) nodes := tx.ReadBucket(nodeBucket)
+7 -8
View File
@@ -902,20 +902,19 @@ func (s *SQLStore) updateEdgeCache(e *models.ChannelEdgePolicy,
// //
// NOTE: part of the Store interface. // NOTE: part of the Store interface.
func (s *SQLStore) ForEachSourceNodeChannel(ctx context.Context, func (s *SQLStore) ForEachSourceNodeChannel(ctx context.Context,
cb func(chanPoint wire.OutPoint, havePolicy bool, v lnwire.GossipVersion, cb func(chanPoint wire.OutPoint,
otherNode *models.Node) error, reset func()) error { havePolicy bool, otherNode *models.Node) error,
reset func()) error {
return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error {
nodeID, nodePub, err := s.getSourceNode( nodeID, nodePub, err := s.getSourceNode(ctx, db, v)
ctx, db, lnwire.GossipVersion1,
)
if err != nil { if err != nil {
return fmt.Errorf("unable to fetch source node: %w", return fmt.Errorf("unable to fetch source node: %w",
err) err)
} }
return forEachNodeChannel( return forEachNodeChannel(
ctx, db, s.cfg, lnwire.GossipVersion1, nodeID, ctx, db, s.cfg, v, nodeID,
func(info *models.ChannelEdgeInfo, func(info *models.ChannelEdgeInfo,
outPolicy *models.ChannelEdgePolicy, outPolicy *models.ChannelEdgePolicy,
_ *models.ChannelEdgePolicy) error { _ *models.ChannelEdgePolicy) error {
@@ -937,8 +936,8 @@ func (s *SQLStore) ForEachSourceNodeChannel(ctx context.Context,
} }
_, otherNode, err := getNodeByPubKey( _, otherNode, err := getNodeByPubKey(
ctx, s.cfg.QueryCfg, db, ctx, s.cfg.QueryCfg, db, v,
lnwire.GossipVersion1, otherNodePub, otherNodePub,
) )
if err != nil { if err != nil {
return fmt.Errorf("unable to fetch "+ return fmt.Errorf("unable to fetch "+
+5 -2
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(
@@ -3576,7 +3576,10 @@ func (s *server) establishPersistentConnections(ctx context.Context) error {
graphAddrs[pubStr] = n graphAddrs[pubStr] = n
return nil return nil
} }
err = s.graphDB.ForEachSourceNodeChannel(
// TODO(elle): for now, we only fetch our V1 channels. This should be
// updated to fetch channels across all versions.
err = s.v1Graph.ForEachSourceNodeChannel(
ctx, forEachSrcNodeChan, func() { ctx, forEachSrcNodeChan, func() {
clear(graphAddrs) clear(graphAddrs)
}, },