github.com/amazechain/amc@v0.1.3/internal/p2p/peers/scorers/peer_status.go (about)

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