github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/p2p/peers/scorers/bad_responses.go (about)

     1  package scorers
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/libp2p/go-libp2p-core/peer"
     7  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers/peerdata"
     8  )
     9  
    10  var _ Scorer = (*BadResponsesScorer)(nil)
    11  
    12  const (
    13  	// DefaultBadResponsesThreshold defines how many bad responses to tolerate before peer is deemed bad.
    14  	DefaultBadResponsesThreshold = 6
    15  	// DefaultBadResponsesDecayInterval defines how often to decay previous statistics.
    16  	// Every interval bad responses counter will be decremented by 1.
    17  	DefaultBadResponsesDecayInterval = time.Hour
    18  )
    19  
    20  // BadResponsesScorer represents bad responses scoring service.
    21  type BadResponsesScorer struct {
    22  	config *BadResponsesScorerConfig
    23  	store  *peerdata.Store
    24  }
    25  
    26  // BadResponsesScorerConfig holds configuration parameters for bad response scoring service.
    27  type BadResponsesScorerConfig struct {
    28  	// Threshold specifies number of bad responses tolerated, before peer is banned.
    29  	Threshold int
    30  	// DecayInterval specifies how often bad response stats should be decayed.
    31  	DecayInterval time.Duration
    32  }
    33  
    34  // newBadResponsesScorer creates new bad responses scoring service.
    35  func newBadResponsesScorer(store *peerdata.Store, config *BadResponsesScorerConfig) *BadResponsesScorer {
    36  	if config == nil {
    37  		config = &BadResponsesScorerConfig{}
    38  	}
    39  	scorer := &BadResponsesScorer{
    40  		config: config,
    41  		store:  store,
    42  	}
    43  	if scorer.config.Threshold == 0 {
    44  		scorer.config.Threshold = DefaultBadResponsesThreshold
    45  	}
    46  	if scorer.config.DecayInterval == 0 {
    47  		scorer.config.DecayInterval = DefaultBadResponsesDecayInterval
    48  	}
    49  	return scorer
    50  }
    51  
    52  // Score returns score (penalty) of bad responses peer produced.
    53  func (s *BadResponsesScorer) Score(pid peer.ID) float64 {
    54  	s.store.RLock()
    55  	defer s.store.RUnlock()
    56  	return s.score(pid)
    57  }
    58  
    59  // score is a lock-free version of Score.
    60  func (s *BadResponsesScorer) score(pid peer.ID) float64 {
    61  	if s.isBadPeer(pid) {
    62  		return BadPeerScore
    63  	}
    64  	score := float64(0)
    65  	peerData, ok := s.store.PeerData(pid)
    66  	if !ok {
    67  		return score
    68  	}
    69  	if peerData.BadResponses > 0 {
    70  		score = float64(peerData.BadResponses) / float64(s.config.Threshold)
    71  		// Since score represents a penalty, negate it.
    72  		score *= -1
    73  	}
    74  	return score
    75  }
    76  
    77  // Params exposes scorer's parameters.
    78  func (s *BadResponsesScorer) Params() *BadResponsesScorerConfig {
    79  	return s.config
    80  }
    81  
    82  // Count obtains the number of bad responses we have received from the given remote peer.
    83  func (s *BadResponsesScorer) Count(pid peer.ID) (int, error) {
    84  	s.store.RLock()
    85  	defer s.store.RUnlock()
    86  	return s.count(pid)
    87  }
    88  
    89  // count is a lock-free version of Count.
    90  func (s *BadResponsesScorer) count(pid peer.ID) (int, error) {
    91  	if peerData, ok := s.store.PeerData(pid); ok {
    92  		return peerData.BadResponses, nil
    93  	}
    94  	return -1, peerdata.ErrPeerUnknown
    95  }
    96  
    97  // Increment increments the number of bad responses we have received from the given remote peer.
    98  // If peer doesn't exist this method is no-op.
    99  func (s *BadResponsesScorer) Increment(pid peer.ID) {
   100  	s.store.Lock()
   101  	defer s.store.Unlock()
   102  
   103  	peerData, ok := s.store.PeerData(pid)
   104  	if !ok {
   105  		s.store.SetPeerData(pid, &peerdata.PeerData{
   106  			BadResponses: 1,
   107  		})
   108  		return
   109  	}
   110  	peerData.BadResponses++
   111  }
   112  
   113  // IsBadPeer states if the peer is to be considered bad.
   114  // If the peer is unknown this will return `false`, which makes using this function easier than returning an error.
   115  func (s *BadResponsesScorer) IsBadPeer(pid peer.ID) bool {
   116  	s.store.RLock()
   117  	defer s.store.RUnlock()
   118  	return s.isBadPeer(pid)
   119  }
   120  
   121  // isBadPeer is lock-free version of IsBadPeer.
   122  func (s *BadResponsesScorer) isBadPeer(pid peer.ID) bool {
   123  	if peerData, ok := s.store.PeerData(pid); ok {
   124  		return peerData.BadResponses >= s.config.Threshold
   125  	}
   126  	return false
   127  }
   128  
   129  // BadPeers returns the peers that are considered bad.
   130  func (s *BadResponsesScorer) BadPeers() []peer.ID {
   131  	s.store.RLock()
   132  	defer s.store.RUnlock()
   133  
   134  	badPeers := make([]peer.ID, 0)
   135  	for pid := range s.store.Peers() {
   136  		if s.isBadPeer(pid) {
   137  			badPeers = append(badPeers, pid)
   138  		}
   139  	}
   140  	return badPeers
   141  }
   142  
   143  // Decay reduces the bad responses of all peers, giving reformed peers a chance to join the network.
   144  // This can be run periodically, although note that each time it runs it does give all bad peers another chance as well
   145  // to clog up the network with bad responses, so should not be run too frequently; once an hour would be reasonable.
   146  func (s *BadResponsesScorer) Decay() {
   147  	s.store.Lock()
   148  	defer s.store.Unlock()
   149  
   150  	for _, peerData := range s.store.Peers() {
   151  		if peerData.BadResponses > 0 {
   152  			peerData.BadResponses--
   153  		}
   154  	}
   155  }