eth: simplify protectedPeers with generic topN helper

Replace peerWithStats wrapper, manual slice copying, and protectTopN
closure with a generic topN[T] function that sorts by score and
returns top elements. protectedPeers now works directly with
[]*p2p.Peer slices, building per-category score functions that close
over the stats map.
This commit is contained in:
Csaba Kiraly 2026-04-10 11:59:48 +02:00
parent 1c518be79f
commit aa5fa692e2

View file

@ -189,6 +189,25 @@ func (cm *dropper) dropRandomPeer() bool {
return true return true
} }
// topN returns the top n elements from items by score (descending).
// Only elements with score > 0 are included. The input slice is not modified.
func topN[T any](items []T, n int, score func(T) float64) []T {
if n == 0 || len(items) == 0 {
return nil
}
cp := make([]T, len(items))
copy(cp, items)
sort.Slice(cp, func(i, j int) bool { return score(cp[i]) > score(cp[j]) })
var result []T
for i := 0; i < n && i < len(cp); i++ {
if score(cp[i]) > 0 {
result = append(result, cp[i])
}
}
return result
}
// protectedPeers computes the set of peers that should not be dropped based // protectedPeers computes the set of peers that should not be dropped based
// on inclusion stats. Each protection category independently selects its // on inclusion stats. Each protection category independently selects its
// top-N peers per inbound/dialed pool; the union is returned. // top-N peers per inbound/dialed pool; the union is returned.
@ -200,42 +219,27 @@ func (cm *dropper) protectedPeers(peers []*p2p.Peer) map[*p2p.Peer]bool {
if len(stats) == 0 { if len(stats) == 0 {
return nil return nil
} }
type peerWithStats struct { // Split peers by direction.
peer *p2p.Peer var inbound, dialed []*p2p.Peer
s PeerInclusionStats
}
var inbound, dialed []peerWithStats
for _, p := range peers { for _, p := range peers {
entry := peerWithStats{p, stats[p.ID().String()]}
if p.Inbound() { if p.Inbound() {
inbound = append(inbound, entry) inbound = append(inbound, p)
} else { } else {
dialed = append(dialed, entry) dialed = append(dialed, p)
} }
} }
result := make(map[*p2p.Peer]bool) result := make(map[*p2p.Peer]bool)
protectTopN := func(entries []peerWithStats, cat protectionCategory) {
n := int(float64(len(entries)) * cat.frac)
if n == 0 {
return
}
sort.Slice(entries, func(i, j int) bool {
return cat.score(entries[i].s) > cat.score(entries[j].s)
})
for i := 0; i < n && i < len(entries); i++ {
if cat.score(entries[i].s) > 0 {
result[entries[i].peer] = true
}
}
}
for _, cat := range protectionCategories { for _, cat := range protectionCategories {
inCopy := make([]peerWithStats, len(inbound)) // Build a score function that looks up the peer's stats.
copy(inCopy, inbound) score := func(p *p2p.Peer) float64 {
dialCopy := make([]peerWithStats, len(dialed)) return cat.score(stats[p.ID().String()])
copy(dialCopy, dialed) }
protectTopN(inCopy, cat) for _, p := range topN(inbound, int(float64(len(inbound))*cat.frac), score) {
protectTopN(dialCopy, cat) result[p] = true
}
for _, p := range topN(dialed, int(float64(len(dialed))*cat.frac), score) {
result[p] = true
}
} }
if len(result) > 0 { if len(result) > 0 {
log.Debug("Protecting high-value peers from drop", "protected", len(result)) log.Debug("Protecting high-value peers from drop", "protected", len(result))