/
peer_status.go
151 lines (133 loc) · 4.34 KB
/
peer_status.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package scorers
import (
"errors"
"math"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/peerdata"
p2ptypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/types"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
)
var _ Scorer = (*PeerStatusScorer)(nil)
// PeerStatusScorer represents scorer that evaluates peers based on their statuses.
// Peer statuses are updated by regularly polling peers (see sync/rpc_status.go).
type PeerStatusScorer struct {
config *PeerStatusScorerConfig
store *peerdata.Store
ourHeadSlot primitives.Slot
highestPeerHeadSlot primitives.Slot
}
// PeerStatusScorerConfig holds configuration parameters for peer status scoring service.
type PeerStatusScorerConfig struct{}
// newPeerStatusScorer creates new peer status scoring service.
func newPeerStatusScorer(store *peerdata.Store, config *PeerStatusScorerConfig) *PeerStatusScorer {
if config == nil {
config = &PeerStatusScorerConfig{}
}
return &PeerStatusScorer{
config: config,
store: store,
}
}
// Score returns calculated peer score.
func (s *PeerStatusScorer) Score(pid peer.ID) float64 {
s.store.RLock()
defer s.store.RUnlock()
return s.score(pid)
}
// score is a lock-free version of Score.
func (s *PeerStatusScorer) score(pid peer.ID) float64 {
if s.isBadPeer(pid) {
return BadPeerScore
}
score := float64(0)
peerData, ok := s.store.PeerData(pid)
if !ok || peerData.ChainState == nil {
return score
}
if peerData.ChainState.HeadSlot < s.ourHeadSlot {
return score
}
// Calculate score as a ratio to the known maximum head slot.
// The closer the current peer's head slot to the maximum, the higher is the calculated score.
if s.highestPeerHeadSlot > 0 {
score = float64(peerData.ChainState.HeadSlot) / float64(s.highestPeerHeadSlot)
return math.Round(score*ScoreRoundingFactor) / ScoreRoundingFactor
}
return score
}
// IsBadPeer states if the peer is to be considered bad.
func (s *PeerStatusScorer) IsBadPeer(pid peer.ID) bool {
s.store.RLock()
defer s.store.RUnlock()
return s.isBadPeer(pid)
}
// isBadPeer is lock-free version of IsBadPeer.
func (s *PeerStatusScorer) isBadPeer(pid peer.ID) bool {
peerData, ok := s.store.PeerData(pid)
if !ok {
return false
}
// Mark peer as bad, if the latest error is one of the terminal ones.
terminalErrs := []error{
p2ptypes.ErrWrongForkDigestVersion,
p2ptypes.ErrInvalidFinalizedRoot,
p2ptypes.ErrInvalidRequest,
}
for _, err := range terminalErrs {
if errors.Is(peerData.ChainStateValidationError, err) {
return true
}
}
return false
}
// BadPeers returns the peers that are considered bad.
func (s *PeerStatusScorer) BadPeers() []peer.ID {
s.store.RLock()
defer s.store.RUnlock()
badPeers := make([]peer.ID, 0)
for pid := range s.store.Peers() {
if s.isBadPeer(pid) {
badPeers = append(badPeers, pid)
}
}
return badPeers
}
// SetPeerStatus sets chain state data for a given peer.
func (s *PeerStatusScorer) SetPeerStatus(pid peer.ID, chainState *pb.Status, validationError error) {
s.store.Lock()
defer s.store.Unlock()
peerData := s.store.PeerDataGetOrCreate(pid)
peerData.ChainState = chainState
peerData.ChainStateLastUpdated = time.Now()
peerData.ChainStateValidationError = validationError
// Update maximum known head slot (scores will be calculated with respect to that maximum value).
if chainState != nil && chainState.HeadSlot > s.highestPeerHeadSlot {
s.highestPeerHeadSlot = chainState.HeadSlot
}
}
// PeerStatus gets the chain state of the given remote peer.
// This can return nil if there is no known chain state for the peer.
// This will error if the peer does not exist.
func (s *PeerStatusScorer) PeerStatus(pid peer.ID) (*pb.Status, error) {
s.store.RLock()
defer s.store.RUnlock()
return s.peerStatus(pid)
}
// peerStatus lock-free version of PeerStatus.
func (s *PeerStatusScorer) peerStatus(pid peer.ID) (*pb.Status, error) {
if peerData, ok := s.store.PeerData(pid); ok {
if peerData.ChainState == nil {
return nil, peerdata.ErrNoPeerStatus
}
return peerData.ChainState, nil
}
return nil, peerdata.ErrPeerUnknown
}
// SetHeadSlot updates known head slot.
func (s *PeerStatusScorer) SetHeadSlot(slot primitives.Slot) {
s.store.Lock()
defer s.store.Unlock()
s.ourHeadSlot = slot
}