From 95ba787417a199f231b4907210918b7412eb7cd2 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 9 Sep 2026 14:41:27 +0000 Subject: [PATCH] policy,state: key the peer map by node ID Adjacency becomes immutable, so a snapshot can resolve peers through its own fresh views instead of storing them. Updates #3417 --- hscontrol/policy/pm.go | 2 +- hscontrol/policy/v2/issue_3212_test.go | 4 +- hscontrol/policy/v2/issue_3233_test.go | 8 +-- hscontrol/policy/v2/policy.go | 14 ++--- hscontrol/policy/v2/policy_test.go | 71 ++++++++------------------ hscontrol/state/node_store.go | 37 ++++++++++---- hscontrol/state/node_store_test.go | 66 ++++++++++++------------ hscontrol/state/state.go | 2 +- 8 files changed, 96 insertions(+), 108 deletions(-) diff --git a/hscontrol/policy/pm.go b/hscontrol/policy/pm.go index 2e459b3e..04269a67 100644 --- a/hscontrol/policy/pm.go +++ b/hscontrol/policy/pm.go @@ -19,7 +19,7 @@ type PolicyManager interface { // MatchersForNode returns matchers for peer relationship determination (unreduced) MatchersForNode(node types.NodeView) ([]matcher.Match, error) // BuildPeerMap constructs peer relationship maps for the given nodes - BuildPeerMap(nodes views.Slice[types.NodeView]) map[types.NodeID][]types.NodeView + BuildPeerMap(nodes views.Slice[types.NodeView]) map[types.NodeID][]types.NodeID SSHPolicy(baseURL string, node types.NodeView) (*tailcfg.SSHPolicy, error) // SSHCheckParams resolves the SSH check period for a (src, dst) pair // from the current policy, avoiding trust of client-provided URL params. diff --git a/hscontrol/policy/v2/issue_3212_test.go b/hscontrol/policy/v2/issue_3212_test.go index 5bbfcaaf..9ac9865a 100644 --- a/hscontrol/policy/v2/issue_3212_test.go +++ b/hscontrol/policy/v2/issue_3212_test.go @@ -90,8 +90,8 @@ func TestIssue3212AutogroupInternetExitVisibility(t *testing.T) { map[types.NodeID]struct{}, len(peerMap[srcNode.ID]), ) - for _, p := range peerMap[srcNode.ID] { - peerIDs[p.ID()] = struct{}{} + for _, id := range peerMap[srcNode.ID] { + peerIDs[id] = struct{}{} } for _, exitName := range exitNames { diff --git a/hscontrol/policy/v2/issue_3233_test.go b/hscontrol/policy/v2/issue_3233_test.go index 2fd049e6..934ef378 100644 --- a/hscontrol/policy/v2/issue_3233_test.go +++ b/hscontrol/policy/v2/issue_3233_test.go @@ -71,15 +71,11 @@ func TestIssue3233ViaInternetExitVisibility(t *testing.T) { peerMap := pm.BuildPeerMap(nodes.ViewSlice()) require.True(t, - slices.ContainsFunc(peerMap[aliceLaptop.ID], func(n types.NodeView) bool { - return n.ID() == aliceExit.ID - }), + slices.Contains(peerMap[aliceLaptop.ID], aliceExit.ID), "alice must see her tag:exit1 exit node as a peer") require.False(t, - slices.ContainsFunc(peerMap[aliceLaptop.ID], func(n types.NodeView) bool { - return n.ID() == bobExit.ID - }), + slices.Contains(peerMap[aliceLaptop.ID], bobExit.ID), "alice must not see bob's tag:exit2 exit node — via grant scopes to tag:exit1") }) diff --git a/hscontrol/policy/v2/policy.go b/hscontrol/policy/v2/policy.go index fc4c13c4..0d45d945 100644 --- a/hscontrol/policy/v2/policy.go +++ b/hscontrol/policy/v2/policy.go @@ -603,7 +603,7 @@ func (pm *PolicyManager) Filter() ([]tailcfg.FilterRule, []matcher.Match) { // up with doing the full work for every node O(n^2), while this will reduce // the list as we see relationships while building the map, making it // O(n^2/2) in the end, but with less work per node. -func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[types.NodeID][]types.NodeView { +func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[types.NodeID][]types.NodeID { if pm == nil { return nil } @@ -627,7 +627,7 @@ func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[typ // Via grants require the per-node path because the global filter // skips via grants (compileFilterRules: if len(grant.Via) > 0 { continue }). if !pm.needsPerNodeFilter { - ret := make(map[types.NodeID][]types.NodeView, nodes.Len()) + ret := make(map[types.NodeID][]types.NodeID, nodes.Len()) // Build the map of all peers according to the matchers. for i := range nodes.Len() { @@ -639,8 +639,8 @@ func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[typ ri, rj := routeInfo[nodes.At(i).ID()], routeInfo[nodes.At(j).ID()] if nodes.At(i).CanAccessWithRoutes(pm.matchers, nodes.At(j), ri.subnet, rj.subnet, rj.isExit) || nodes.At(j).CanAccessWithRoutes(pm.matchers, nodes.At(i), rj.subnet, ri.subnet, ri.isExit) { - ret[nodes.At(i).ID()] = append(ret[nodes.At(i).ID()], nodes.At(j)) - ret[nodes.At(j).ID()] = append(ret[nodes.At(j).ID()], nodes.At(i)) + ret[nodes.At(i).ID()] = append(ret[nodes.At(i).ID()], nodes.At(j).ID()) + ret[nodes.At(j).ID()] = append(ret[nodes.At(j).ID()], nodes.At(i).ID()) } } } @@ -649,7 +649,7 @@ func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[typ } // For autogroup:self or via grants, build per-node peer relationships - ret := make(map[types.NodeID][]types.NodeView, nodes.Len()) + ret := make(map[types.NodeID][]types.NodeID, nodes.Len()) // Pre-compute per-node matchers using unreduced compiled rules // We need unreduced rules to determine peer relationships correctly. @@ -692,8 +692,8 @@ func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[typ canIReachJ := hasFilterJ && nodeI.CanAccessWithRoutes(matchersJ, nodeJ, riI.subnet, riJ.subnet, riJ.isExit) if canIAccessJ || canJAccessI || canJReachI || canIReachJ { - ret[nodeI.ID()] = append(ret[nodeI.ID()], nodeJ) - ret[nodeJ.ID()] = append(ret[nodeJ.ID()], nodeI) + ret[nodeI.ID()] = append(ret[nodeI.ID()], nodeJ.ID()) + ret[nodeJ.ID()] = append(ret[nodeJ.ID()], nodeI.ID()) } } } diff --git a/hscontrol/policy/v2/policy_test.go b/hscontrol/policy/v2/policy_test.go index fc99162c..07187e2f 100644 --- a/hscontrol/policy/v2/policy_test.go +++ b/hscontrol/policy/v2/policy_test.go @@ -741,9 +741,8 @@ func TestAutogroupSelfWithOtherRules(t *testing.T) { test1Peers := peerMap[test1Node.ID] // Verify test-1 can see the router (group:home -> tag:node-router rule) - require.True(t, slices.ContainsFunc(test1Peers, func(n types.NodeView) bool { - return n.ID() == test2RouterNode.ID - }), "test-1 should see test-2's router via group:home -> tag:node-router rule, even when autogroup:self rule exists (issue #2838)") + require.True(t, slices.Contains(test1Peers, test2RouterNode.ID), + "test-1 should see test-2's router via group:home -> tag:node-router rule, even when autogroup:self rule exists (issue #2838)") // Verify that test-1 has filter rules (including autogroup:self and tag:node-router access) rules, err := pm.FilterForNode(test1Node.View()) @@ -903,12 +902,12 @@ func TestTagPropagationToPeerMap(t *testing.T) { // Check user2's peers - should include user1 user2Peers := initialPeerMap[user2Node.ID] require.Len(t, user2Peers, 1, "user2 should have 1 peer initially (user1 with tag:web)") - require.Equal(t, user1Node.ID, user2Peers[0].ID(), "user2's peer should be user1") + require.Equal(t, user1Node.ID, user2Peers[0], "user2's peer should be user1") // Check user1's peers - should include user2 (bidirectional ACL) user1Peers := initialPeerMap[user1Node.ID] require.Len(t, user1Peers, 1, "user1 should have 1 peer initially (user2)") - require.Equal(t, user2Node.ID, user1Peers[0].ID(), "user1's peer should be user2") + require.Equal(t, user2Node.ID, user1Peers[0], "user1's peer should be user2") // Now change user1's tags: remove tag:web, keep only tag:internal user1NodeUpdated := &types.Node{ @@ -1035,17 +1034,15 @@ func TestAutogroupSelfWithAdminOverride(t *testing.T) { // Admin should see the tagged server as a peer (via group:admin -> *:* rule) adminPeers := peerMap[adminNode.ID] - require.True(t, slices.ContainsFunc(adminPeers, func(n types.NodeView) bool { - return n.ID() == user1TaggedNode.ID - }), "admin should see tagged server as peer via *:* rule (issue #2990)") + require.True(t, slices.Contains(adminPeers, user1TaggedNode.ID), + "admin should see tagged server as peer via *:* rule (issue #2990)") // Tagged server should also see admin as a peer (symmetric visibility) // Even though tagged server cannot ACCESS admin, it should still SEE admin // because admin CAN access it. This is required for proper network operation. taggedPeers := peerMap[user1TaggedNode.ID] - require.True(t, slices.ContainsFunc(taggedPeers, func(n types.NodeView) bool { - return n.ID() == adminNode.ID - }), "tagged server should see admin as peer (symmetric visibility)") + require.True(t, slices.Contains(taggedPeers, adminNode.ID), + "tagged server should see admin as peer (symmetric visibility)") } // TestAutogroupSelfSymmetricVisibility verifies that peer visibility is symmetric: @@ -1108,16 +1105,14 @@ func TestAutogroupSelfSymmetricVisibility(t *testing.T) { // Device A (user1) should see device B (tag:web) as peer aPeers := peerMap[deviceA.ID] - require.True(t, slices.ContainsFunc(aPeers, func(n types.NodeView) bool { - return n.ID() == deviceB.ID - }), "device A should see device B as peer (user1 -> tag:web rule)") + require.True(t, slices.Contains(aPeers, deviceB.ID), + "device A should see device B as peer (user1 -> tag:web rule)") // Device B (tag:web) should ALSO see device A as peer (symmetric visibility) // Even though B cannot ACCESS A, B should still SEE A as a peer bPeers := peerMap[deviceB.ID] - require.True(t, slices.ContainsFunc(bPeers, func(n types.NodeView) bool { - return n.ID() == deviceA.ID - }), "device B should see device A as peer (symmetric visibility)") + require.True(t, slices.Contains(bPeers, deviceA.ID), + "device B should see device A as peer (symmetric visibility)") } // TestAutogroupSelfDoesNotBreakOtherUsersAccess reproduces the Discord scenario @@ -1253,11 +1248,7 @@ func TestAutogroupSelfDoesNotBreakOtherUsersAccess(t *testing.T) { // Helper to check if node A sees node B canSee := func(a, b types.NodeID) bool { - peers := peerMap[a] - - return slices.ContainsFunc(peers, func(n types.NodeView) bool { - return n.ID() == b - }) + return slices.Contains(peerMap[a], b) } // Superadmin should see all tagged servers @@ -1357,16 +1348,14 @@ func TestEmptyFilterNodesStillVisible(t *testing.T) { // Admin should see the tagged server adminPeers := peerMap[adminDevice.ID] - require.True(t, slices.ContainsFunc(adminPeers, func(n types.NodeView) bool { - return n.ID() == taggedServer.ID - }), "admin should see tagged server") + require.True(t, slices.Contains(adminPeers, taggedServer.ID), + "admin should see tagged server") // Tagged server should see admin (symmetric visibility) // Even though the server has no outbound rules (empty filter) serverPeers := peerMap[taggedServer.ID] - require.True(t, slices.ContainsFunc(serverPeers, func(n types.NodeView) bool { - return n.ID() == adminDevice.ID - }), "tagged server should see admin (symmetric visibility)") + require.True(t, slices.Contains(serverPeers, adminDevice.ID), + "tagged server should see admin (symmetric visibility)") } // TestAutogroupSelfCombinedWithTags verifies that autogroup:self combined with @@ -1434,11 +1423,7 @@ func TestAutogroupSelfCombinedWithTags(t *testing.T) { // Helper to check visibility canSee := func(a, b types.NodeID) bool { - peers := peerMap[a] - - return slices.ContainsFunc(peers, func(n types.NodeView) bool { - return n.ID() == b - }) + return slices.Contains(peerMap[a], b) } // Admin laptop should see: admin phone (autogroup:self) AND web server (tag:web) @@ -1527,11 +1512,7 @@ func TestIssue2990SameUserTaggedDevice(t *testing.T) { peerMap := pm.BuildPeerMap(nodes.ViewSlice()) canSee := func(a, b types.NodeID) bool { - peers := peerMap[a] - - return slices.ContainsFunc(peers, func(n types.NodeView) bool { - return n.ID() == b - }) + return slices.Contains(peerMap[a], b) } // node1 should see node2 (via group:admin -> *:* and symmetric visibility) @@ -2205,9 +2186,7 @@ func TestBuildPeerMap_AutogroupInternetMakesExitNodeVisible(t *testing.T) { peerMap := pm.BuildPeerMap(nodes.ViewSlice()) require.True(t, - slices.ContainsFunc(peerMap[aliceNode.ID], func(n types.NodeView) bool { - return n.ID() == exitNode.ID - }), + slices.Contains(peerMap[aliceNode.ID], exitNode.ID), "alice should see the exit node as a peer when an ACL grants autogroup:internet (#3212)") _, matchers := pm.Filter() @@ -2527,12 +2506,6 @@ func TestPeerRelayGrantMakesRelayVisible(t *testing.T) { }, } - containsID := func(peers []types.NodeView, id types.NodeID) bool { - return slices.ContainsFunc(peers, func(nv types.NodeView) bool { - return nv.ID() == id - }) - } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pm, err := NewPolicyManager( @@ -2543,10 +2516,10 @@ func TestPeerRelayGrantMakesRelayVisible(t *testing.T) { peerMap := pm.BuildPeerMap(tt.nodes.ViewSlice()) for _, srcID := range tt.srcIDs { - require.True(t, containsID(peerMap[srcID], tt.relayID), + require.True(t, slices.Contains(peerMap[srcID], tt.relayID), "node %d must see relay %d via cap/relay alone", srcID, tt.relayID) - require.True(t, containsID(peerMap[tt.relayID], srcID), + require.True(t, slices.Contains(peerMap[tt.relayID], srcID), "relay %d must see node %d via cap/relay alone", tt.relayID, srcID) } diff --git a/hscontrol/state/node_store.go b/hscontrol/state/node_store.go index cece54ed..050c979e 100644 --- a/hscontrol/state/node_store.go +++ b/hscontrol/state/node_store.go @@ -148,13 +148,18 @@ func NewNodeStore(allNodes types.Nodes, peersFunc PeersFunc, batchSize int, batc type Snapshot struct { // nodesByID is the main source of truth for nodes. nodesByID map[types.NodeID]types.Node + // nodeViewsByID resolves peer adjacency IDs to immutable views from this + // snapshot without rebuilding every peer slice on each write. + nodeViewsByID map[types.NodeID]types.NodeView // calculated from nodesByID nodesByNodeKey map[key.NodePublic]types.NodeView nodesByMachineKey map[key.MachinePublic]map[types.UserID]types.NodeView - peersByNode map[types.NodeID][]types.NodeView - nodesByUser map[types.UserID][]types.NodeView - allNodes []types.NodeView + // peersByNode stores immutable adjacency as IDs; ListPeers resolves the + // corresponding views through nodeViewsByID. + peersByNode map[types.NodeID][]types.NodeID + nodesByUser map[types.UserID][]types.NodeView + allNodes []types.NodeView // routes maps each prefix to its current primary advertiser. The // previous assignment is carried over when still valid so the @@ -167,7 +172,7 @@ type Snapshot struct { // with the relationships between nodes and their peers. // This will typically be used to calculate which nodes can see each other // based on the current policy. -type PeersFunc func(nodes []types.NodeView) map[types.NodeID][]types.NodeView +type PeersFunc func(nodes []types.NodeView) map[types.NodeID][]types.NodeID // work represents a single operation to be performed on the [NodeStore]. type work struct { @@ -608,14 +613,19 @@ func snapshotFromNodes( defer timer.ObserveDuration() allNodes := make([]types.NodeView, 0, len(nodes)) + nodeViewsByID := make(map[types.NodeID]types.NodeView, len(nodes)) + for _, n := range nodes { - allNodes = append(allNodes, n.View()) + nv := n.View() + allNodes = append(allNodes, nv) + nodeViewsByID[n.ID] = nv } routes, isPrimaryRoute := electPrimaryRoutes(nodes, prevRoutes) newSnap := Snapshot{ nodesByID: nodes, + nodeViewsByID: nodeViewsByID, allNodes: allNodes, nodesByNodeKey: make(map[key.NodePublic]types.NodeView), nodesByMachineKey: make(map[key.MachinePublic]map[types.UserID]types.NodeView), @@ -624,7 +634,7 @@ func snapshotFromNodes( // it will use the list of all nodes, combined with the // current policy to precalculate which nodes are peers and // can see each other. - peersByNode: func() map[types.NodeID][]types.NodeView { + peersByNode: func() map[types.NodeID][]types.NodeID { peersTimer := prometheus.NewTimer(nodeStorePeersCalculationDuration) defer peersTimer.ObserveDuration() @@ -638,7 +648,7 @@ func snapshotFromNodes( // Build nodesByUser, nodesByNodeKey, and nodesByMachineKey maps for _, n := range nodes { - nodeView := n.View() + nodeView := nodeViewsByID[n.ID] userID := n.TypedUserID() // Tagged nodes are owned by their tags, not a user, @@ -649,7 +659,6 @@ func snapshotFromNodes( newSnap.nodesByNodeKey[n.NodeKey] = nodeView - // Build machine key index if newSnap.nodesByMachineKey[n.MachineKey] == nil { newSnap.nodesByMachineKey[n.MachineKey] = make(map[types.UserID]types.NodeView) } @@ -882,7 +891,17 @@ func (s *NodeStore) ListPeers(id types.NodeID) views.Slice[types.NodeView] { nodeStoreOperations.WithLabelValues("list_peers").Inc() - return views.SliceOf(s.data.Load().peersByNode[id]) + snapshot := s.data.Load() + peerIDs := snapshot.peersByNode[id] + peers := make([]types.NodeView, 0, len(peerIDs)) + + for _, peerID := range peerIDs { + if peer, ok := snapshot.nodeViewsByID[peerID]; ok { + peers = append(peers, peer) + } + } + + return views.SliceOf(peers) } // PrimaryRouteFor returns the current primary advertiser for prefix. diff --git a/hscontrol/state/node_store_test.go b/hscontrol/state/node_store_test.go index 1fd04c38..210e27c1 100644 --- a/hscontrol/state/node_store_test.go +++ b/hscontrol/state/node_store_test.go @@ -26,8 +26,8 @@ func TestSnapshotFromNodes(t *testing.T) { name: "empty nodes", setupFunc: func() (map[types.NodeID]types.Node, PeersFunc) { nodes := make(map[types.NodeID]types.Node) - peersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeView { - return make(map[types.NodeID][]types.NodeView) + peersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeID { + return make(map[types.NodeID][]types.NodeID) } return nodes, peersFunc @@ -79,9 +79,9 @@ func TestSnapshotFromNodes(t *testing.T) { // Each node sees the other as peer (but not itself) assert.Len(t, snapshot.peersByNode[1], 1) - assert.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0].ID()) + assert.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0]) assert.Len(t, snapshot.peersByNode[2], 1) - assert.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0].ID()) + assert.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0]) assert.Len(t, snapshot.nodesByUser[1], 2) }, }, @@ -133,17 +133,17 @@ func TestSnapshotFromNodes(t *testing.T) { // Odd nodes should only see other odd nodes as peers require.Len(t, snapshot.peersByNode[1], 1) - assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0].ID()) + assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0]) require.Len(t, snapshot.peersByNode[3], 1) - assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0].ID()) + assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0]) // Even nodes should only see other even nodes as peers require.Len(t, snapshot.peersByNode[2], 1) - assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0].ID()) + assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0]) require.Len(t, snapshot.peersByNode[4], 1) - assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0].ID()) + assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0]) }, }, } @@ -190,14 +190,14 @@ func createTestNode(nodeID types.NodeID, userID uint, username, hostname string) // Peer functions -func allowAllPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeView { - ret := make(map[types.NodeID][]types.NodeView, len(nodes)) +func allowAllPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeID { + ret := make(map[types.NodeID][]types.NodeID, len(nodes)) for _, node := range nodes { - var peers []types.NodeView + var peers []types.NodeID for _, n := range nodes { if n.ID() != node.ID() { - peers = append(peers, n) + peers = append(peers, n.ID()) } } @@ -207,10 +207,10 @@ func allowAllPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeView return ret } -func oddEvenPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeView { - ret := make(map[types.NodeID][]types.NodeView, len(nodes)) +func oddEvenPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeID { + ret := make(map[types.NodeID][]types.NodeID, len(nodes)) for _, node := range nodes { - var peers []types.NodeView + var peers []types.NodeID nodeIsOdd := node.ID()%2 == 1 @@ -223,7 +223,7 @@ func oddEvenPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeView // Only add peer if both are odd or both are even if nodeIsOdd == peerIsOdd { - peers = append(peers, n) + peers = append(peers, n.ID()) } } @@ -313,9 +313,9 @@ func TestNodeStoreOperations(t *testing.T) { // Now both nodes should see each other as peers assert.Len(t, snapshot.peersByNode[1], 1) - assert.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0].ID()) + assert.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0]) assert.Len(t, snapshot.peersByNode[2], 1) - assert.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0].ID()) + assert.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0]) assert.Len(t, snapshot.nodesByUser[1], 2) }, }, @@ -382,9 +382,9 @@ func TestNodeStoreOperations(t *testing.T) { // Remaining nodes should see each other as peers assert.Len(t, snapshot.peersByNode[1], 1) - assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0].ID()) + assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0]) assert.Len(t, snapshot.peersByNode[3], 1) - assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0].ID()) + assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0]) // User groupings updated assert.Len(t, snapshot.nodesByUser[1], 1) // user1 now has only node 1 @@ -475,16 +475,16 @@ func TestNodeStoreOperations(t *testing.T) { // Verify odd-even peer relationships require.Len(t, snapshot.peersByNode[1], 1) - assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0].ID()) + assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0]) require.Len(t, snapshot.peersByNode[2], 1) - assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0].ID()) + assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0]) require.Len(t, snapshot.peersByNode[3], 1) - assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0].ID()) + assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0]) require.Len(t, snapshot.peersByNode[4], 1) - assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0].ID()) + assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0]) }, }, { @@ -500,9 +500,9 @@ func TestNodeStoreOperations(t *testing.T) { // Even nodes should still see each other require.Len(t, snapshot.peersByNode[2], 1) - assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0].ID()) + assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0]) require.Len(t, snapshot.peersByNode[4], 1) - assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0].ID()) + assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0]) }, }, }, @@ -1225,16 +1225,16 @@ func TestRebuildPeerMapsWithChangedPeersFunc(t *testing.T) { // This simulates how PolicyManager.BuildPeerMap works - it reads state // that can change between calls - dynamicPeersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeView { - ret := make(map[types.NodeID][]types.NodeView, len(nodes)) + dynamicPeersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeID { + ret := make(map[types.NodeID][]types.NodeID, len(nodes)) if allowPeers { // Allow all peers for _, node := range nodes { - var peers []types.NodeView + var peers []types.NodeID for _, n := range nodes { if n.ID() != node.ID() { - peers = append(peers, n) + peers = append(peers, n.ID()) } } @@ -1243,7 +1243,7 @@ func TestRebuildPeerMapsWithChangedPeersFunc(t *testing.T) { } else { // Allow no peers for _, node := range nodes { - ret[node.ID()] = []types.NodeView{} + ret[node.ID()] = []types.NodeID{} } } @@ -1265,8 +1265,8 @@ func TestRebuildPeerMapsWithChangedPeersFunc(t *testing.T) { snapshot := store.data.Load() require.Len(t, snapshot.peersByNode[1], 1, "node1 should have 1 peer initially") require.Len(t, snapshot.peersByNode[2], 1, "node2 should have 1 peer initially") - require.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0].ID()) - require.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0].ID()) + require.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0]) + require.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0]) // Now "change the policy" by disabling peers allowPeers = false diff --git a/hscontrol/state/state.go b/hscontrol/state/state.go index 3b33e1df..1a321707 100644 --- a/hscontrol/state/state.go +++ b/hscontrol/state/state.go @@ -272,7 +272,7 @@ func NewState(cfg *types.Config) (*State, error) { // This moves the complex peer relationship logic into the policy package where it belongs. nodeStore := NewNodeStore( nodes, - func(nodes []types.NodeView) map[types.NodeID][]types.NodeView { + func(nodes []types.NodeView) map[types.NodeID][]types.NodeID { return polMan.BuildPeerMap(views.SliceOf(nodes)) }, batchSize,