From 3e7cd8c2f152404afe53c8e17692b46363b49e7b Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Tue, 6 Jan 2026 21:31:05 +0530 Subject: [PATCH] protect one peer per topic --- beacon-chain/p2p/gossip_peer_controller.go | 24 +++++++++---------- .../p2p/gossip_peer_controller_test.go | 16 ++++++------- beacon-chain/p2p/gossipcrawler/interface.go | 2 +- beacon-chain/sync/subscriber.go | 5 ++-- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/beacon-chain/p2p/gossip_peer_controller.go b/beacon-chain/p2p/gossip_peer_controller.go index 806b42b637..8aa711bab9 100644 --- a/beacon-chain/p2p/gossip_peer_controller.go +++ b/beacon-chain/p2p/gossip_peer_controller.go @@ -303,33 +303,31 @@ func (g *GossipPeerDialer) peersForTopic(topic string, targetCount int) []*enode return newPeers } -// SoleProviderPeers returns peer IDs that are the sole provider for at least one topic. -// A peer is considered a sole provider if it's the only connected peer for a topic (listPeers returns only this peer) -// -// These peers should be protected from pruning since losing them would mean -// losing connectivity to that topic entirely. -func (g *GossipPeerDialer) SoleProviderPeers() []peer.ID { +// ProtectedPeers returns peer IDs that should be protected from pruning. +// For each topic, one connected peer is marked as protected to ensure +// we maintain connectivity to all subscribed topics. +func (g *GossipPeerDialer) ProtectedPeers() []peer.ID { if g.topicsProvider == nil { return nil } topics := g.topicsProvider() - soleProviders := make(map[peer.ID]struct{}) + protectedPeers := make(map[peer.ID]struct{}) for topic := range topics { connectedPeers := g.listPeers(topic) - // Skip if no peers or more than one connected peer - if len(connectedPeers) != 1 { + // Skip if no peers connected + if len(connectedPeers) == 0 { continue } - // This peer is the sole known provider - soleProviders[connectedPeers[0]] = struct{}{} + // Protect the first peer for this topic + protectedPeers[connectedPeers[0]] = struct{}{} } - result := make([]peer.ID, 0, len(soleProviders)) - for pid := range soleProviders { + result := make([]peer.ID, 0, len(protectedPeers)) + for pid := range protectedPeers { result = append(result, pid) } return result diff --git a/beacon-chain/p2p/gossip_peer_controller_test.go b/beacon-chain/p2p/gossip_peer_controller_test.go index 5433e8c5d8..a60ce57feb 100644 --- a/beacon-chain/p2p/gossip_peer_controller_test.go +++ b/beacon-chain/p2p/gossip_peer_controller_test.go @@ -442,7 +442,7 @@ func TestGossipPeerDialer_selectPeersForTopics(t *testing.T) { } } -func TestGossipPeerDialer_SoleProviderPeers(t *testing.T) { +func TestGossipPeerDialer_ProtectedPeers(t *testing.T) { peerA := peer.ID("peerA") peerB := peer.ID("peerB") peerC := peer.ID("peerC") @@ -472,10 +472,10 @@ func TestGossipPeerDialer_SoleProviderPeers(t *testing.T) { expected: []peer.ID{}, }, { - name: "multiple peers for all topics", + name: "multiple peers for all topics protects first peer from each", topicsProvider: func() map[string]int { return map[string]int{"topic/a": 2, "topic/b": 2} }, connectedPeers: map[string][]peer.ID{"topic/a": {peerA, peerB}, "topic/b": {peerB, peerC}}, - expected: []peer.ID{}, + expected: []peer.ID{peerA, peerB}, }, { name: "single peer for one topic", @@ -484,22 +484,22 @@ func TestGossipPeerDialer_SoleProviderPeers(t *testing.T) { expected: []peer.ID{peerA}, }, { - name: "same peer is sole provider for multiple topics", + name: "same peer is first for multiple topics", topicsProvider: func() map[string]int { return map[string]int{"topic/a": 1, "topic/b": 1} }, connectedPeers: map[string][]peer.ID{"topic/a": {peerA}, "topic/b": {peerA}}, expected: []peer.ID{peerA}, }, { - name: "different sole providers for different topics", + name: "different first peers for different topics", topicsProvider: func() map[string]int { return map[string]int{"topic/a": 1, "topic/b": 1} }, connectedPeers: map[string][]peer.ID{"topic/a": {peerA}, "topic/b": {peerB}}, expected: []peer.ID{peerA, peerB}, }, { - name: "mix of single and multiple peers", + name: "protects first peer from each topic", topicsProvider: func() map[string]int { return map[string]int{"topic/a": 1, "topic/b": 2, "topic/c": 1} }, connectedPeers: map[string][]peer.ID{"topic/a": {peerA}, "topic/b": {peerB, peerC}, "topic/c": {peerC}}, - expected: []peer.ID{peerA, peerC}, + expected: []peer.ID{peerA, peerB, peerC}, }, } @@ -514,7 +514,7 @@ func TestGossipPeerDialer_SoleProviderPeers(t *testing.T) { listPeers: listPeers, } - got := dialer.SoleProviderPeers() + got := dialer.ProtectedPeers() if tt.expected == nil { require.Nil(t, got) diff --git a/beacon-chain/p2p/gossipcrawler/interface.go b/beacon-chain/p2p/gossipcrawler/interface.go index 1e672e65a2..b4ecc5908f 100644 --- a/beacon-chain/p2p/gossipcrawler/interface.go +++ b/beacon-chain/p2p/gossipcrawler/interface.go @@ -33,5 +33,5 @@ type SubnetTopicsProvider func() map[string]int type GossipDialer interface { Start(provider SubnetTopicsProvider) error DialPeersForTopicBlocking(ctx context.Context, topic string, nPeers int) error - SoleProviderPeers() []peer.ID + ProtectedPeers() []peer.ID } diff --git a/beacon-chain/sync/subscriber.go b/beacon-chain/sync/subscriber.go index e68f300bcd..6f73bc7c64 100644 --- a/beacon-chain/sync/subscriber.go +++ b/beacon-chain/sync/subscriber.go @@ -248,9 +248,8 @@ func (s *Service) filterNeededPeers(pids []peer.ID) []peer.ID { dialer := s.cfg.p2p.GossipDialer() if dialer != nil { - // Protect peers that are the sole provider for any gossip topic. - // These peers should not be pruned since we have no alternative. - for _, pid := range dialer.SoleProviderPeers() { + // ask the dialer for peers that should be protected from pruning. + for _, pid := range dialer.ProtectedPeers() { peerMap[pid] = true } }