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