protect one peer per topic

This commit is contained in:
aarshkshah1992
2026-01-06 21:31:05 +05:30
parent ea1962bf17
commit 3e7cd8c2f1
4 changed files with 22 additions and 25 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}
}