From dfe0d3f2e55b5581a5653dcd651501041b8b947b Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 9 Sep 2026 14:46:09 +0000 Subject: [PATCH] state: stop broadcasting a whole peer on disconnect Going offline changes nothing the policy reads, so the row write skips the policy refresh and peers get only the offline patch. Updates #3417 --- hscontrol/state/connect_test.go | 5 +++++ hscontrol/state/state.go | 21 ++++++++------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/hscontrol/state/connect_test.go b/hscontrol/state/connect_test.go index be73b5f2..235d092c 100644 --- a/hscontrol/state/connect_test.go +++ b/hscontrol/state/connect_test.go @@ -270,6 +270,11 @@ func TestDisconnectOutOfOrderSessionsCannotStrandNodeOnline(t *testing.T) { require.NoError(t, err) assert.True(t, hasPeerPatch(cs), "final release must emit the offline peer patch") + for _, c := range cs { + assert.Empty(t, c.PeersChanged, + "disconnect must not fan out a whole-peer update, got %+v", c) + } + nv, ok = s.GetNodeByID(nodeID) require.True(t, ok) diff --git a/hscontrol/state/state.go b/hscontrol/state/state.go index 2de8aea3..a6d69534 100644 --- a/hscontrol/state/state.go +++ b/hscontrol/state/state.go @@ -727,23 +727,18 @@ func (s *State) Disconnect(id types.NodeID, epoch uint64) ([]change.Change, erro log.Info().EmbedObject(node).Msg("node disconnected") // Persist LastSeen best-effort: [NodeStore] already reflects offline - // and peers still need the change notifications below. - _, c, err := s.persistNodeAndRefreshPolicy(node) + // and peers still need the change notifications below. Going offline + // changes nothing the policy reads, so the row write skips the policy + // manager refresh. + _, err := s.persistNode(node) if err != nil { log.Error().Err(err).EmbedObject(node).Msg("failed to update last seen in database") - - c = change.Change{} } - // Only a node whose online state changes what peers compute (a subnet - // router, relay target, or via target) needs a full peer recompute. - // An ordinary node going offline just sends the lightweight offline - // patch; emitting a PolicyChange for it would force every peer to - // rebuild its netmap on every disconnect. - // A node going offline sends a lightweight offline peer patch. Subnet - // routers and other recompute-forcing nodes rely on the gated - // PolicyChange below for the peer recompute, so no full update here. - cs := []change.Change{change.NodeOffline(node.ID()), c} + // An ordinary node going offline only needs the lightweight offline + // patch. Subnet routers, relay targets, and via targets change what + // peers compute, so they additionally force a peer recompute. + cs := []change.Change{change.NodeOffline(node.ID())} if s.polMan.NodeNeedsPeerRecompute(node) { cs = append(cs, change.PolicyChange()) }